v2-0001-Support-adding-partitioned-tables-to-publication.patch
application/octet-stream
Filename: v2-0001-Support-adding-partitioned-tables-to-publication.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v2-0001
Subject: Support adding partitioned tables to publication
| File | + | − |
|---|---|---|
| doc/src/sgml/logical-replication.sgml | 15 | 7 |
| doc/src/sgml/ref/alter_publication.sgml | 9 | 2 |
| doc/src/sgml/ref/create_publication.sgml | 7 | 5 |
| src/backend/catalog/pg_publication.c | 71 | 18 |
| src/backend/commands/publicationcmds.c | 272 | 122 |
| src/backend/commands/tablecmds.c | 12 | 1 |
| src/backend/executor/execReplication.c | 8 | 11 |
| src/backend/replication/logical/tablesync.c | 17 | 4 |
| src/bin/pg_dump/pg_dump.c | 15 | 5 |
| src/include/catalog/pg_publication.h | 4 | 2 |
| src/include/catalog/pg_publication_rel.h | 1 | 0 |
| src/include/commands/publicationcmds.h | 2 | 0 |
| src/include/replication/logicalproto.h | 1 | 0 |
| src/test/regress/expected/publication.out | 145 | 7 |
| src/test/regress/sql/publication.sql | 76 | 4 |
From 110244aa38bc27da051c0b13ee3a79d689ccaa2c Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 2 Oct 2019 18:52:49 +0900
Subject: [PATCH v2] Support adding partitioned tables to publication
Adding a partitioned table to a publication in turn adds all of its
existing and future partitions. Detaching a partition doesn't remove
it from the publication, but its membership is dissociated from
the parent's membership, that is, it becomes a standalone member.
---
doc/src/sgml/logical-replication.sgml | 22 +-
doc/src/sgml/ref/alter_publication.sgml | 11 +-
doc/src/sgml/ref/create_publication.sgml | 12 +-
src/backend/catalog/pg_publication.c | 89 +++++--
src/backend/commands/publicationcmds.c | 394 +++++++++++++++++++---------
src/backend/commands/tablecmds.c | 13 +-
src/backend/executor/execReplication.c | 19 +-
src/backend/replication/logical/tablesync.c | 21 +-
src/bin/pg_dump/pg_dump.c | 20 +-
src/include/catalog/pg_publication.h | 6 +-
src/include/catalog/pg_publication_rel.h | 1 +
src/include/commands/publicationcmds.h | 2 +
src/include/replication/logicalproto.h | 1 +
src/test/regress/expected/publication.out | 152 ++++++++++-
src/test/regress/sql/publication.sql | 80 +++++-
15 files changed, 655 insertions(+), 188 deletions(-)
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index f657d1d06e..c14861ddfb 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -402,13 +402,21 @@
<listitem>
<para>
- Replication is only possible from base tables to base tables. That is,
- the tables on the publication and on the subscription side must be normal
- tables, not views, materialized views, partition root tables, or foreign
- tables. In the case of partitions, you can therefore replicate a
- partition hierarchy one-to-one, but you cannot currently replicate to a
- differently partitioned setup. Attempts to replicate tables other than
- base tables will result in an error.
+ Replication is only possible between combinations of regular and
+ partitioned tables. That is, the tables on the publication and on the
+ subscription side must be normal or partitioned tables, not views,
+ materialized views, or foreign tables. Attempts to replicate tables other
+ than regular and partitioned tables will result in an error.
+ </para>
+
+ <para>
+ Actually, when a partitioned table is added to a publication, all of its
+ existing and future partitions are automatically added to the publication.
+ Any changes made to the leaf partitions are sent to the subscription server
+ which must contain a partitioned table with partition hierarchy matching
+ one-to-one with the publication side partitioned table. For partitioned
+ tables on the two sides to match one-to-one, each partition with a given
+ partition constraint must have the same name on both sides.
</para>
</listitem>
</itemizedlist>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index 534e598d93..e9db773d9b 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -46,7 +46,11 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
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.
+ to become effective. Using <literal>DROP TABLE</literal> to remove a
+ partitioned table from a publication will also remove all of its partitions
+ from the publication unless <literal>ONLY</literal> is specified. However,
+ removing a partition from a publication without first removing its parent
+ will result in an error.
</para>
<para>
@@ -91,7 +95,10 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
table name, only that table is affected. If <literal>ONLY</literal> is not
specified, the table and all its descendant tables (if any) are
affected. Optionally, <literal>*</literal> can be specified after the table
- name to explicitly indicate that descendant tables are included.
+ name to explicitly indicate that descendant tables are included. Specifying
+ <literal>ONLY</literal> with <literal>SET TABLE</literal> will result in an
+ error for a partitioned table if it contains partitions, because partitions
+ must be added to the publication too.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index 99f87ca393..7354665e47 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -72,11 +72,13 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
- Only persistent base tables can be part of a publication. Temporary
- tables, unlogged tables, foreign tables, materialized views, regular
- views, and partitioned tables cannot be part of a publication. To
- replicate a partitioned table, add the individual partitions to the
- publication.
+ Only persistent base and partitioned tables can be part of a publication.
+ Temporary tables, unlogged tables, foreign tables, materialized views,
+ regular views cannot be part of a publication. Specifying
+ <literal>ONLY</literal> results in an error for a partitioned table if
+ it contains partitions, because partitions must be added to the
+ publication too. See <xref linkend="logical-replication-publication"/>
+ for details about how partitioned tables are replicated.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index fd5da7d5f7..2547cb71f8 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -50,17 +50,9 @@
static void
check_publication_add_relation(Relation targetrel)
{
- /* Give more specific error for partitioned tables */
- if (RelationGetForm(targetrel)->relkind == RELKIND_PARTITIONED_TABLE)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("\"%s\" is a partitioned table",
- RelationGetRelationName(targetrel)),
- errdetail("Adding partitioned tables to publications is not supported."),
- errhint("You can add the table partitions individually.")));
-
/* Must be table */
- if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION)
+ if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION &&
+ RelationGetForm(targetrel)->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("\"%s\" is not a table",
@@ -106,7 +98,8 @@ check_publication_add_relation(Relation targetrel)
static bool
is_publishable_class(Oid relid, Form_pg_class reltuple)
{
- return reltuple->relkind == RELKIND_RELATION &&
+ return (reltuple->relkind == RELKIND_RELATION ||
+ reltuple->relkind == RELKIND_PARTITIONED_TABLE) &&
!IsCatalogRelationOid(relid) &&
reltuple->relpersistence == RELPERSISTENCE_PERMANENT &&
relid >= FirstNormalObjectId;
@@ -144,13 +137,56 @@ pg_relation_is_publishable(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(result);
}
+/*
+ * Update prinh flag for a given relation's pg_publication_rel entry
+ */
+void
+publication_rel_update_inheritance(Relation pubrelCatalog, Oid pubid,
+ Relation rel, bool inh)
+{
+ Oid relid = RelationGetRelid(rel);
+ HeapTuple tuple;
+ Form_pg_publication_rel prform;
+
+ Assert(pubrelCatalog != NULL);
+
+ tuple = SearchSysCache2(PUBLICATIONRELMAP, ObjectIdGetDatum(relid),
+ ObjectIdGetDatum(pubid));
+ Assert(tuple != NULL);
+
+ prform = (Form_pg_publication_rel) GETSTRUCT(tuple);
+ if (prform->prinh != inh)
+ {
+ Datum newValues[Natts_pg_publication_rel];
+ bool newNulls[Natts_pg_publication_rel];
+ bool replaces[Natts_pg_publication_rel];
+ HeapTuple newTuple;
+
+ MemSet(newValues, 0, sizeof(newValues));
+ MemSet(newNulls, false, sizeof(newValues));
+ MemSet(replaces, false, sizeof(replaces));
+ newValues[Anum_pg_publication_rel_prinh - 1] = inh;
+ newNulls[Anum_pg_publication_rel_prinh - 1] = false;
+ replaces[Anum_pg_publication_rel_prinh - 1] = true;
+
+ newTuple = heap_modify_tuple(tuple,
+ RelationGetDescr(pubrelCatalog),
+ newValues, newNulls,
+ replaces);
+ CatalogTupleUpdate(pubrelCatalog, &newTuple->t_self, newTuple);
+ heap_freetuple(newTuple);
+ }
+
+ ReleaseSysCache(tuple);
+}
+
/*
* Insert new publication / relation mapping.
*/
ObjectAddress
publication_add_relation(Oid pubid, Relation targetrel,
- bool if_not_exists)
+ bool inh, bool if_not_exists)
{
Relation rel;
HeapTuple tup;
@@ -172,10 +208,20 @@ publication_add_relation(Oid pubid, Relation targetrel,
if (SearchSysCacheExists2(PUBLICATIONRELMAP, ObjectIdGetDatum(relid),
ObjectIdGetDatum(pubid)))
{
- table_close(rel, RowExclusiveLock);
-
- if (if_not_exists)
+ if (if_not_exists || inh)
+ {
+ /*
+ * It's possible that the target relation is being re-added to the
+ * publication due to inheritance recursion. In that case, simply
+ * set the inheritance flag of the found entry. Note that the
+ * flag is turned off when the partition is detached from the
+ * parent.
+ */
+ if (inh)
+ publication_rel_update_inheritance(rel, pubid, targetrel, inh);
+ table_close(rel, RowExclusiveLock);
return InvalidObjectAddress;
+ }
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
@@ -196,6 +242,9 @@ publication_add_relation(Oid pubid, Relation targetrel,
ObjectIdGetDatum(pubid);
values[Anum_pg_publication_rel_prrelid - 1] =
ObjectIdGetDatum(relid);
+ /* Set inheritance only for partitions. */
+ values[Anum_pg_publication_rel_prinh - 1] =
+ BoolGetDatum(inh && targetrel->rd_rel->relispartition);
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
@@ -254,9 +303,12 @@ GetRelationPublications(Oid relid)
*
* This should only be used for normal publications, the FOR ALL TABLES
* should use GetAllTablesPublicationRelations().
+ *
+ * Return partitions that were added to the publication via parent only
+ * if 'get_children' is true.
*/
List *
-GetPublicationRelations(Oid pubid)
+GetPublicationRelations(Oid pubid, bool get_children)
{
List *result;
Relation pubrelsrel;
@@ -282,7 +334,8 @@ GetPublicationRelations(Oid pubid)
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
- result = lappend_oid(result, pubrel->prrelid);
+ if (!pubrel->prinh || get_children)
+ result = lappend_oid(result, pubrel->prrelid);
}
systable_endscan(scan);
@@ -497,7 +550,7 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
if (publication->alltables)
tables = GetAllTablesPublicationRelations();
else
- tables = GetPublicationRelations(publication->oid);
+ tables = GetPublicationRelations(publication->oid, true);
funcctx->user_fctx = (void *) tables;
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index f115d4bf80..9bd85b13de 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -37,6 +37,10 @@
#include "commands/event_trigger.h"
#include "commands/publicationcmds.h"
+#include "nodes/makefuncs.h"
+
+#include "partitioning/partdesc.h"
+
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
@@ -50,11 +54,16 @@
/* 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,
+static void PublicationAddTables(Oid pubid, List *tables, bool if_not_exists,
AlterPublicationStmt *stmt);
+static void PublicationAddTable(Oid pubid, Relation rel, bool if_not_exists,
+ AlterPublicationStmt *stmt,
+ bool recurse, bool recursing,
+ List **processed_relids);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationDropTable(Oid pubid, Relation rel, bool missing_ok,
+ bool recurse, bool recursing,
+ List **processed_relids);
static void
parse_publication_options(List *options,
@@ -219,13 +228,8 @@ CreatePublication(CreatePublicationStmt *stmt)
if (stmt->tables)
{
- List *rels;
-
Assert(list_length(stmt->tables) > 0);
-
- rels = OpenTableList(stmt->tables);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddTables(puboid, stmt->tables, true, NULL);
}
table_close(rel, RowExclusiveLock);
@@ -303,7 +307,7 @@ AlterPublicationOptions(AlterPublicationStmt *stmt, Relation rel,
}
else
{
- List *relids = GetPublicationRelations(pubform->oid);
+ List *relids = GetPublicationRelations(pubform->oid, true);
/*
* We don't want to send too many individual messages, at some point
@@ -338,7 +342,6 @@ static void
AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
HeapTuple tup)
{
- List *rels = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
@@ -352,15 +355,18 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
Assert(list_length(stmt->tables) > 0);
- rels = OpenTableList(stmt->tables);
-
if (stmt->tableAction == DEFELEM_ADD)
- PublicationAddTables(pubid, rels, false, stmt);
+ PublicationAddTables(pubid, stmt->tables, false, stmt);
else if (stmt->tableAction == DEFELEM_DROP)
- PublicationDropTables(pubid, rels, false);
+ PublicationDropTables(pubid, stmt->tables, false);
else /* DEFELEM_SET */
{
- List *oldrelids = GetPublicationRelations(pubid);
+ /*
+ * Only fetch parent relations, because child relations cannot
+ * be dropped on their own as we might do based on the logic
+ * below.
+ */
+ List *oldrelids = GetPublicationRelations(pubid, false);
List *delrels = NIL;
ListCell *oldlc;
@@ -371,11 +377,13 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
ListCell *newlc;
bool found = false;
- foreach(newlc, rels)
+ foreach(newlc, stmt->tables)
{
- Relation newrel = (Relation) lfirst(newlc);
+ RangeVar *newrel = (RangeVar *) lfirst(newlc);
- if (RelationGetRelid(newrel) == oldrelid)
+ if (RangeVarGetRelid(newrel,
+ ShareUpdateExclusiveLock,
+ false) == oldrelid)
{
found = true;
break;
@@ -384,10 +392,10 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
if (!found)
{
- Relation oldrel = table_open(oldrelid,
- ShareUpdateExclusiveLock);
+ RangeVar *oldrelrv = makeRangeVar(get_namespace_name(get_rel_namespace(oldrelid)),
+ get_rel_name(oldrelid), -1);
- delrels = lappend(delrels, oldrel);
+ delrels = lappend(delrels, oldrelrv);
}
}
@@ -398,12 +406,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
- PublicationAddTables(pubid, rels, true, stmt);
-
- CloseTableList(delrels);
+ PublicationAddTables(pubid, stmt->tables, true, stmt);
}
-
- CloseTableList(rels);
}
/*
@@ -501,19 +505,17 @@ RemovePublicationRelById(Oid proid)
}
/*
- * Open relations specified by a RangeVar list.
- * The returned tables are locked in ShareUpdateExclusiveLock mode.
+ * Add listed tables to the publication.
*/
-static List *
-OpenTableList(List *tables)
+static void
+PublicationAddTables(Oid pubid, List *tables, bool if_not_exists,
+ AlterPublicationStmt *stmt)
{
List *relids = NIL;
- List *rels = NIL;
ListCell *lc;
- /*
- * Open, share-lock, and check all the explicitly-specified relations
- */
+ Assert(!stmt || !stmt->for_all_tables);
+
foreach(lc, tables)
{
RangeVar *rv = castNode(RangeVar, lfirst(lc));
@@ -540,129 +542,221 @@ OpenTableList(List *tables)
continue;
}
- rels = lappend(rels, rel);
+ /* Add to processed list. */
relids = lappend_oid(relids, myrelid);
- /* Add children of this rel, if requested */
- if (recurse)
- {
- List *children;
- ListCell *child;
+ PublicationAddTable(pubid, rel, if_not_exists,
+ stmt, recurse, false, &relids);
- children = find_all_inheritors(myrelid, ShareUpdateExclusiveLock,
- NULL);
-
- foreach(child, children)
- {
- Oid childrelid = lfirst_oid(child);
-
- /* Allow query cancel in case this takes a long time */
- CHECK_FOR_INTERRUPTS();
-
- /*
- * Skip duplicates if user specified both parent and child
- * tables.
- */
- if (list_member_oid(relids, childrelid))
- continue;
-
- /* find_all_inheritors already got lock */
- rel = table_open(childrelid, NoLock);
- rels = lappend(rels, rel);
- relids = lappend_oid(relids, childrelid);
- }
- }
- }
-
- list_free(relids);
-
- return rels;
-}
-
-/*
- * Close all relations in the list.
- */
-static void
-CloseTableList(List *rels)
-{
- ListCell *lc;
-
- foreach(lc, rels)
- {
- Relation rel = (Relation) lfirst(lc);
-
- table_close(rel, NoLock);
+ table_close(rel, ShareUpdateExclusiveLock);
}
}
/*
- * Add listed tables to the publication.
+ * Add given table and children (if any) to the publication.
*/
static void
-PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt)
+PublicationAddTable(Oid pubid, Relation rel, bool if_not_exists,
+ AlterPublicationStmt *stmt,
+ bool recurse, bool recursing,
+ List **processed_relids)
{
- ListCell *lc;
+ ObjectAddress obj;
+ Oid relid = RelationGetRelid(rel);
- Assert(!stmt || !stmt->for_all_tables);
+ /* Must be owner of the table or superuser. */
+ if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
+ RelationGetRelationName(rel));
- foreach(lc, rels)
+ obj = publication_add_relation(pubid, rel, recursing, if_not_exists);
+ if (stmt)
{
- Relation rel = (Relation) lfirst(lc);
- ObjectAddress obj;
+ EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+ (Node *) stmt);
- /* Must be owner of the table or superuser. */
- if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
- RelationGetRelationName(rel));
+ InvokeObjectPostCreateHook(PublicationRelRelationId,
+ obj.objectId, 0);
+ }
- obj = publication_add_relation(pubid, rel, if_not_exists);
- if (stmt)
+ /* Process children of this rel, if requested */
+ if (recurse)
+ {
+ List *children;
+ ListCell *child;
+
+ children = find_all_inheritors(relid, ShareUpdateExclusiveLock,
+ NULL);
+
+ foreach(child, children)
{
- EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
- (Node *) stmt);
+ Oid childrelid = lfirst_oid(child);
- InvokeObjectPostCreateHook(PublicationRelRelationId,
- obj.objectId, 0);
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+
+ /*
+ * Skip duplicates if user specified both parent and child
+ * tables.
+ */
+ if (list_member_oid(*processed_relids, childrelid))
+ continue;
+
+ /* find_all_inheritors already got lock */
+ rel = table_open(childrelid, NoLock);
+
+ /* Add to processed list. */
+ *processed_relids = lappend_oid(*processed_relids, childrelid);
+
+ /* Recursively add this child to the publication. */
+ PublicationAddTable(pubid, rel, if_not_exists, stmt,
+ recurse, true, processed_relids);
+ table_close(rel, NoLock);
}
}
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ rel->rd_partdesc->nparts > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot add only partitioned table to publication when partitions exist"),
+ errhint("Do not specify the ONLY keyword.")));
}
/*
* Remove listed tables from the publication.
*/
static void
-PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
+PublicationDropTables(Oid pubid, List *tables, bool missing_ok)
{
- ObjectAddress obj;
- ListCell *lc;
- Oid prid;
+ ListCell *lc;
+ List *relids = NIL;
- foreach(lc, rels)
+ foreach(lc, tables)
{
- Relation rel = (Relation) lfirst(lc);
- Oid relid = RelationGetRelid(rel);
+ RangeVar *rv = castNode(RangeVar, lfirst(lc));
+ bool recurse = rv->inh;
+ Relation rel;
+ Oid myrelid;
- prid = GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
- ObjectIdGetDatum(relid),
- ObjectIdGetDatum(pubid));
- if (!OidIsValid(prid))
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+
+ rel = table_openrv(rv, ShareUpdateExclusiveLock);
+ myrelid = RelationGetRelid(rel);
+
+ /*
+ * 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))
{
- if (missing_ok)
- continue;
-
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("relation \"%s\" is not part of the publication",
- RelationGetRelationName(rel))));
+ table_close(rel, ShareUpdateExclusiveLock);
+ continue;
}
- ObjectAddressSet(obj, PublicationRelRelationId, prid);
- performDeletion(&obj, DROP_CASCADE, 0);
+ /* Add to processed list. */
+ relids = lappend_oid(relids, myrelid);
+
+ PublicationDropTable(pubid, rel, missing_ok, recurse, false, &relids);
+
+ table_close(rel, ShareUpdateExclusiveLock);
}
}
/*
+ * Remove given table and children (if any) from the publication.
+ */
+static void
+PublicationDropTable(Oid pubid, Relation rel, bool missing_ok,
+ bool recurse, bool recursing,
+ List **processed_relids)
+{
+ ObjectAddress obj;
+ Oid relid = RelationGetRelid(rel);
+ Relation pubrelCatalog;
+ HeapTuple tuple;
+ Form_pg_publication_rel prform;
+ List *children;
+ ListCell *child;
+
+ pubrelCatalog = table_open(PublicationRelRelationId, RowExclusiveLock);
+ tuple = SearchSysCache2(PUBLICATIONRELMAP,
+ ObjectIdGetDatum(relid),
+ ObjectIdGetDatum(pubid));
+ if (!HeapTupleIsValid(tuple))
+ {
+ if (missing_ok)
+ return;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("relation \"%s\" is not part of the publication",
+ RelationGetRelationName(rel))));
+ }
+
+ prform = (Form_pg_publication_rel) GETSTRUCT(tuple);
+
+ /*
+ * For a partition, check if we can really drop it from the
+ * publication.
+ */
+ if (rel->rd_rel->relispartition && prform->prinh && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot drop partition \"%s\" from an inherited publication",
+ RelationGetRelationName(rel)),
+ errhint("Drop the parent from publication instead.")));
+
+ ObjectAddressSet(obj, PublicationRelRelationId, prform->oid);
+ performDeletion(&obj, DROP_CASCADE, 0);
+ ReleaseSysCache(tuple);
+
+ /* Process children of this rel, if requested */
+ children = find_all_inheritors(relid, ShareUpdateExclusiveLock,
+ NULL);
+
+ foreach(child, children)
+ {
+ Oid childrelid = lfirst_oid(child);
+ Relation childrel;
+
+ /* Allow query cancel in case this takes a long time */
+ CHECK_FOR_INTERRUPTS();
+
+ /*
+ * Skip duplicates if user specified both parent and child
+ * tables.
+ */
+ if (list_member_oid(*processed_relids, childrelid))
+ continue;
+
+ /* find_all_inheritors already got lock */
+ childrel = table_open(childrelid, NoLock);
+
+ /* Add to processed list. */
+ *processed_relids = lappend_oid(*processed_relids, childrelid);
+
+ /*
+ * If requested, recursively drop this child from the publication.
+ * Otherwise, simply reset the inheritance flag of child's publication
+ * membership because the parent is no longer part of the publication.
+ */
+ if (recurse)
+ PublicationDropTable(pubid, childrel, missing_ok, recurse, true,
+ processed_relids);
+ else
+ publication_rel_update_inheritance(pubrelCatalog, pubid, childrel,
+ false);
+ table_close(childrel, NoLock);
+ }
+
+ table_close(pubrelCatalog, NoLock);
+}
+
+/*
* Internal workhorse for changing a publication owner
*/
static void
@@ -772,3 +866,59 @@ AlterPublicationOwner_oid(Oid subid, Oid newOwnerId)
table_close(rel, RowExclusiveLock);
}
+
+/*
+ * This adds the partition and its sub-partitions (if any) to all of
+ * parent's publications. Partition's membership of those publications
+ * is attached to parent's membership, so for example, partition cannot
+ * be removed from the publication unless parent is also removed.
+ */
+void
+ClonePublicationsForPartition(Relation parent, Relation partition)
+{
+ ListCell *lc;
+ List *parentPubs = GetRelationPublications(RelationGetRelid(parent));
+
+ /* Add partition and its sub-partitions (if any) to each publication. */
+ foreach(lc, parentPubs)
+ {
+ Oid pubid = lfirst_oid(lc);
+ List *relids = list_make1_oid(RelationGetRelid(partition));
+
+ PublicationAddTable(pubid, partition, true, NULL, true, true,
+ &relids);
+ }
+}
+
+/*
+ * This marks partition's membership in parent's publications as standalone,
+ * that is, not attached to the parent's membership in those publications.
+ * This function is called when detaching a partition from its parent.
+ */
+void
+DetachPartitionPublications(Relation parent, Relation partition)
+{
+ ListCell *lc;
+ List *parentPubs = GetRelationPublications(RelationGetRelid(parent));
+ Relation pubrelCatalog = NULL;
+
+ if (parentPubs != NIL)
+ pubrelCatalog = table_open(PublicationRelRelationId,
+ RowExclusiveLock);
+
+ /*
+ * For each publication, mark the partition's membership as no longer
+ * being inherited from parent. Note that we don't recurse to
+ * partition's own partitions.
+ */
+ foreach(lc, parentPubs)
+ {
+ Oid pubid = lfirst_oid(lc);
+
+ publication_rel_update_inheritance(pubrelCatalog, pubid, partition,
+ false);
+ }
+
+ if (pubrelCatalog)
+ table_close(pubrelCatalog, NoLock);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ba8f4459f3..39cf0d40d5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -53,6 +53,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"
@@ -1044,7 +1045,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* If we're creating a partition, create now all the indexes, triggers,
- * FKs defined in the parent.
+ * FKs defined in the parent. Also, add the partition to all
+ * publications that the parent is part of.
*
* We can't do it earlier, because DefineIndex wants to know the partition
* key which we just stored.
@@ -1117,6 +1119,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
*/
CloneForeignKeyConstraints(NULL, parent, rel);
+ /* Add the partition to any publications that parent is part of. */
+ ClonePublicationsForPartition(parent, rel);
+
table_close(parent, NoLock);
}
@@ -15673,6 +15678,9 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
*/
CloneForeignKeyConstraints(wqueue, rel, attachrel);
+ /* Add the partition to any publications that parent is part of. */
+ ClonePublicationsForPartition(rel, attachrel);
+
/*
* Generate partition constraint from the partition bound specification.
* If the parent itself is a partition, make sure to include its
@@ -16253,6 +16261,9 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
}
CommandCounterIncrement();
+ /* Mark partition's membership in parent's publications as local. */
+ DetachPartitionPublications(rel, partRel);
+
/*
* Invalidate the parent's relcache so that the partition is no longer
* included in its partition descriptor.
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 95e027c970..f05f44c99f 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -591,17 +591,10 @@ CheckSubscriptionRelkind(char relkind, const char *nspname,
const char *relname)
{
/*
- * We currently only support writing to regular tables. However, give a
- * more specific error for partitioned and foreign tables.
+ * We currently only support writing to regular and partitioned tables.
+ * However, give a more specific error for foreign tables.
*/
- if (relkind == RELKIND_PARTITIONED_TABLE)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot use relation \"%s.%s\" as logical replication target",
- nspname, relname),
- errdetail("\"%s.%s\" is a partitioned table.",
- nspname, relname)));
- else if (relkind == RELKIND_FOREIGN_TABLE)
+ if (relkind == RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot use relation \"%s.%s\" as logical replication target",
@@ -609,7 +602,11 @@ CheckSubscriptionRelkind(char relkind, const char *nspname,
errdetail("\"%s.%s\" is a foreign table.",
nspname, relname)));
- if (relkind != RELKIND_RELATION)
+ /*
+ * Subscription for partitioned tables are really placeholder objects, as
+ * replication itself occurs on the individual partition level.
+ */
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot use relation \"%s.%s\" as logical replication target",
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 7881079e96..ed081743a9 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -646,7 +646,7 @@ fetch_remote_table_info(char *nspname, char *relname,
WalRcvExecResult *res;
StringInfoData cmd;
TupleTableSlot *slot;
- Oid tableRow[2] = {OIDOID, CHAROID};
+ Oid tableRow[3] = {OIDOID, CHAROID, CHAROID};
Oid attrRow[4] = {TEXTOID, OIDOID, INT4OID, BOOLOID};
bool isnull;
int natt;
@@ -656,16 +656,16 @@ fetch_remote_table_info(char *nspname, char *relname,
/* First fetch Oid and replica identity. */
initStringInfo(&cmd);
- appendStringInfo(&cmd, "SELECT c.oid, c.relreplident"
+ appendStringInfo(&cmd, "SELECT c.oid, c.relreplident, c.relkind"
" FROM pg_catalog.pg_class c"
" INNER JOIN pg_catalog.pg_namespace n"
" ON (c.relnamespace = n.oid)"
" WHERE n.nspname = %s"
" AND c.relname = %s"
- " AND c.relkind = 'r'",
+ " AND pg_relation_is_publishable(c.oid)",
quote_literal_cstr(nspname),
quote_literal_cstr(relname));
- res = walrcv_exec(wrconn, cmd.data, 2, tableRow);
+ res = walrcv_exec(wrconn, cmd.data, 3, tableRow);
if (res->status != WALRCV_OK_TUPLES)
ereport(ERROR,
@@ -682,6 +682,8 @@ fetch_remote_table_info(char *nspname, char *relname,
Assert(!isnull);
lrel->replident = DatumGetChar(slot_getattr(slot, 2, &isnull));
Assert(!isnull);
+ lrel->relkind = DatumGetChar(slot_getattr(slot, 3, &isnull));
+ Assert(!isnull);
ExecDropSingleTupleTableSlot(slot);
walrcv_clear_result(res);
@@ -769,6 +771,17 @@ copy_table(Relation rel)
relmapentry = logicalrep_rel_open(lrel.remoteid, NoLock);
Assert(rel == relmapentry->localrel);
+ /*
+ * Can't copy if either of the local and the remote relation is a
+ * partitioned table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+ lrel.relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ logicalrep_rel_close(relmapentry, NoLock);
+ return;
+ }
+
/* Start copy on the publisher. */
initStringInfo(&cmd);
appendStringInfo(&cmd, "COPY %s TO STDOUT",
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index f01fea5b91..5ef01faeed 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -3972,8 +3972,9 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
{
TableInfo *tbinfo = &tblinfo[i];
- /* Only plain tables can be aded to publications. */
- if (tbinfo->relkind != RELKIND_RELATION)
+ /* Only plain and partitioned tables can be aded to publications. */
+ if (tbinfo->relkind != RELKIND_RELATION &&
+ tbinfo->relkind != RELKIND_PARTITIONED_TABLE)
continue;
/*
@@ -3989,12 +3990,16 @@ getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables)
resetPQExpBuffer(query);
- /* Get the publication membership for the table. */
+ /*
+ * Get the publication membership for the table. Skip publications
+ * for which it has been added as a child via inheritance.
+ */
appendPQExpBuffer(query,
"SELECT pr.tableoid, pr.oid, p.pubname "
"FROM pg_publication_rel pr, pg_publication p "
"WHERE pr.prrelid = '%u'"
- " AND p.oid = pr.prpubid",
+ " AND p.oid = pr.prpubid"
+ " AND NOT prinh",
tbinfo->dobj.catId.oid);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -4053,7 +4058,12 @@ dumpPublicationTable(Archive *fout, PublicationRelInfo *pubrinfo)
query = createPQExpBuffer();
- appendPQExpBuffer(query, "ALTER PUBLICATION %s ADD TABLE ONLY",
+ /* For partitioned tables, recurse by default to add partitions. */
+ if (pubrinfo->pubtable->relkind == RELKIND_PARTITIONED_TABLE)
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ADD TABLE",
+ fmtId(pubrinfo->pubname));
+ else
+ appendPQExpBuffer(query, "ALTER PUBLICATION %s ADD TABLE ONLY",
fmtId(pubrinfo->pubname));
appendPQExpBuffer(query, " %s;\n",
fmtQualifiedDumpable(tbinfo));
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 20a2f0ac1b..4a0b17806f 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -81,13 +81,15 @@ typedef struct Publication
extern Publication *GetPublication(Oid pubid);
extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
extern List *GetRelationPublications(Oid relid);
-extern List *GetPublicationRelations(Oid pubid);
+extern List *GetPublicationRelations(Oid pubid, bool get_children);
extern List *GetAllTablesPublications(void);
extern List *GetAllTablesPublicationRelations(void);
extern bool is_publishable_relation(Relation rel);
extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
- bool if_not_exists);
+ bool inh, bool if_not_exists);
+void publication_rel_update_inheritance(Relation pubrelCatalog, Oid pubid,
+ Relation rel, bool inh);
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_rel.h b/src/include/catalog/pg_publication_rel.h
index 5f5bc92ab3..46e5039a12 100644
--- a/src/include/catalog/pg_publication_rel.h
+++ b/src/include/catalog/pg_publication_rel.h
@@ -31,6 +31,7 @@ CATALOG(pg_publication_rel,6106,PublicationRelRelationId)
Oid oid; /* oid */
Oid prpubid; /* Oid of the publication */
Oid prrelid; /* Oid of the relation */
+ bool prinh; /* Is relation added due to inheritance? */
} FormData_pg_publication_rel;
/* ----------------
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index c536b648f8..915b1e0056 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -25,5 +25,7 @@ extern void RemovePublicationRelById(Oid proid);
extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
+extern void ClonePublicationsForPartition(Relation parent, Relation partition);
+extern void DetachPartitionPublications(Relation parent, Relation partition);
#endif /* PUBLICATIONCMDS_H */
diff --git a/src/include/replication/logicalproto.h b/src/include/replication/logicalproto.h
index 3fc430af01..0fea368d99 100644
--- a/src/include/replication/logicalproto.h
+++ b/src/include/replication/logicalproto.h
@@ -45,6 +45,7 @@ typedef struct LogicalRepRelation
LogicalRepRelId remoteid; /* unique id of the relation */
char *nspname; /* schema name */
char *relname; /* relation name */
+ char relkind; /* relation kind */
int natts; /* number of columns */
char **attnames; /* column names */
Oid *atttyps; /* column types */
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index feb51e4add..bf378782f8 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -47,7 +47,6 @@ CREATE SCHEMA pub_test;
CREATE TABLE testpub_tbl1 (id serial primary key, data text);
CREATE TABLE pub_test.testpub_nopk (foo int, bar int);
CREATE VIEW testpub_view AS SELECT 1;
-CREATE TABLE testpub_parted (a int) PARTITION BY LIST (a);
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub_foralltables FOR ALL TABLES WITH (publish = 'insert');
RESET client_min_messages;
@@ -142,11 +141,151 @@ Tables:
ALTER PUBLICATION testpub_default ADD TABLE testpub_view;
ERROR: "testpub_view" is not a table
DETAIL: Only tables can be added to publications.
--- fail - partitioned table
-ALTER PUBLICATION testpub_fortbl ADD TABLE testpub_parted;
-ERROR: "testpub_parted" is a partitioned table
-DETAIL: Adding partitioned tables to publications is not supported.
-HINT: You can add the table partitions individually.
+--
+-- Tests for partitioned tables
+--
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forparted;
+RESET client_min_messages;
+CREATE TABLE testpub_parted (a int) PARTITION BY LIST (a);
+-- Can add "only" partitioned table when there are no partitions
+ALTER PUBLICATION testpub_forparted ADD TABLE ONLY testpub_parted;
+\dRp+ testpub_forparted
+ Publication testpub_forparted
+ Owner | All tables | Inserts | Updates | Deletes | Truncates
+--------------------------+------------+---------+---------+---------+-----------
+ regress_publication_user | f | t | t | t | t
+Tables:
+ "public.testpub_parted"
+
+ALTER PUBLICATION testpub_forparted DROP TABLE testpub_parted;
+CREATE TABLE testpub_parted1 partition of testpub_parted FOR VALUES IN (1) PARTITION BY LIST (a);
+CREATE TABLE testpub_parted11 partition of testpub_parted1 FOR VALUES IN (1);
+-- fail - cannot add "only" partitioned table
+ALTER PUBLICATION testpub_forparted ADD TABLE ONLY testpub_parted;
+ERROR: cannot add only partitioned table to publication when partitions exist
+HINT: Do not specify the ONLY keyword.
+-- ok, should add partition testpub_parted1, sub-partition testpub_parted11
+ALTER PUBLICATION testpub_forparted ADD TABLE testpub_parted;
+\dRp+ testpub_forparted
+ Publication testpub_forparted
+ Owner | All tables | Inserts | Updates | Deletes | Truncates
+--------------------------+------------+---------+---------+---------+-----------
+ regress_publication_user | f | t | t | t | t
+Tables:
+ "public.testpub_parted"
+ "public.testpub_parted1"
+ "public.testpub_parted11"
+
+-- New partitions should automatically get added to the publications that
+-- the parent is in.
+CREATE TABLE testpub_parted2 PARTITION OF testpub_parted FOR VALUES IN (2);
+\dRp+ testpub_forparted
+ Publication testpub_forparted
+ Owner | All tables | Inserts | Updates | Deletes | Truncates
+--------------------------+------------+---------+---------+---------+-----------
+ regress_publication_user | f | t | t | t | t
+Tables:
+ "public.testpub_parted"
+ "public.testpub_parted1"
+ "public.testpub_parted11"
+ "public.testpub_parted2"
+
+-- Create a table that will later be attached to the parent and add it to
+-- the same publication as the one that the parent is in
+CREATE TABLE testpub_parted3 (LIKE testpub_parted);
+ALTER PUBLICATION testpub_forparted ADD TABLE testpub_parted3;
+\dRp+ testpub_forparted
+ Publication testpub_forparted
+ Owner | All tables | Inserts | Updates | Deletes | Truncates
+--------------------------+------------+---------+---------+---------+-----------
+ regress_publication_user | f | t | t | t | t
+Tables:
+ "public.testpub_parted"
+ "public.testpub_parted1"
+ "public.testpub_parted11"
+ "public.testpub_parted2"
+ "public.testpub_parted3"
+
+-- Attaching to parent should not result in the table being duplicatively
+-- added to the publication, nor an error
+ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted3 FOR VALUES IN (3);
+\dRp+ testpub_forparted
+ Publication testpub_forparted
+ Owner | All tables | Inserts | Updates | Deletes | Truncates
+--------------------------+------------+---------+---------+---------+-----------
+ regress_publication_user | f | t | t | t | t
+Tables:
+ "public.testpub_parted"
+ "public.testpub_parted1"
+ "public.testpub_parted11"
+ "public.testpub_parted2"
+ "public.testpub_parted3"
+
+-- cannot drop a partition from publication which parent is still part of
+ALTER PUBLICATION testpub_forparted DROP TABLE testpub_parted1;
+ERROR: cannot drop partition "testpub_parted1" from an inherited publication
+HINT: Drop the parent from publication instead.
+-- When the partition is detached, it and sub-partitions continue to be
+-- members of the publication
+ALTER TABLE testpub_parted DETACH PARTITION testpub_parted1;
+\dRp+ testpub_forparted
+ Publication testpub_forparted
+ Owner | All tables | Inserts | Updates | Deletes | Truncates
+--------------------------+------------+---------+---------+---------+-----------
+ regress_publication_user | f | t | t | t | t
+Tables:
+ "public.testpub_parted"
+ "public.testpub_parted1"
+ "public.testpub_parted11"
+ "public.testpub_parted2"
+ "public.testpub_parted3"
+
+-- sub-partition's membership is still inherited, so can't drop
+ALTER PUBLICATION testpub_forparted DROP TABLE testpub_parted11;
+ERROR: cannot drop partition "testpub_parted11" from an inherited publication
+HINT: Drop the parent from publication instead.
+-- dropping the now-detached partition should work though
+ALTER PUBLICATION testpub_forparted DROP TABLE testpub_parted1;
+\dRp+ testpub_forparted
+ Publication testpub_forparted
+ Owner | All tables | Inserts | Updates | Deletes | Truncates
+--------------------------+------------+---------+---------+---------+-----------
+ regress_publication_user | f | t | t | t | t
+Tables:
+ "public.testpub_parted"
+ "public.testpub_parted2"
+ "public.testpub_parted3"
+
+-- Some tests for SET TABLE
+-- no changes to the membership, because testpub_parted and testpub_parted3
+-- are already in the publication
+ALTER PUBLICATION testpub_forparted SET TABLE testpub_parted, testpub_parted3;
+\dRp+ testpub_forparted
+ Publication testpub_forparted
+ Owner | All tables | Inserts | Updates | Deletes | Truncates
+--------------------------+------------+---------+---------+---------+-----------
+ regress_publication_user | f | t | t | t | t
+Tables:
+ "public.testpub_parted"
+ "public.testpub_parted2"
+ "public.testpub_parted3"
+
+-- This should drop testpub_parted (hence, testpub_parted2, testpub_parted3)
+-- and add testpub_parted2, testpub_parted1 (hence testpub_parted11)
+ALTER PUBLICATION testpub_forparted SET TABLE testpub_parted2, testpub_parted1;
+\dRp+ testpub_forparted
+ Publication testpub_forparted
+ Owner | All tables | Inserts | Updates | Deletes | Truncates
+--------------------------+------------+---------+---------+---------+-----------
+ regress_publication_user | f | t | t | t | t
+Tables:
+ "public.testpub_parted1"
+ "public.testpub_parted11"
+ "public.testpub_parted2"
+
+DROP PUBLICATION testpub_forparted;
+DROP TABLE testpub_parted, testpub_parted1;
ALTER PUBLICATION testpub_default ADD TABLE testpub_tbl1;
ALTER PUBLICATION testpub_default SET TABLE testpub_tbl1;
ALTER PUBLICATION testpub_default ADD TABLE pub_test.testpub_nopk;
@@ -219,7 +358,6 @@ ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok
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
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 5773a755cf..06ece9e338 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -35,7 +35,6 @@ CREATE SCHEMA pub_test;
CREATE TABLE testpub_tbl1 (id serial primary key, data text);
CREATE TABLE pub_test.testpub_nopk (foo int, bar int);
CREATE VIEW testpub_view AS SELECT 1;
-CREATE TABLE testpub_parted (a int) PARTITION BY LIST (a);
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub_foralltables FOR ALL TABLES WITH (publish = 'insert');
@@ -83,8 +82,82 @@ CREATE PUBLICATION testpub_fortbl FOR TABLE testpub_tbl1;
-- fail - view
ALTER PUBLICATION testpub_default ADD TABLE testpub_view;
--- fail - partitioned table
-ALTER PUBLICATION testpub_fortbl ADD TABLE testpub_parted;
+
+--
+-- Tests for partitioned tables
+--
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION testpub_forparted;
+RESET client_min_messages;
+
+CREATE TABLE testpub_parted (a int) PARTITION BY LIST (a);
+
+-- Can add "only" partitioned table when there are no partitions
+ALTER PUBLICATION testpub_forparted ADD TABLE ONLY testpub_parted;
+
+\dRp+ testpub_forparted
+
+ALTER PUBLICATION testpub_forparted DROP TABLE testpub_parted;
+
+CREATE TABLE testpub_parted1 partition of testpub_parted FOR VALUES IN (1) PARTITION BY LIST (a);
+CREATE TABLE testpub_parted11 partition of testpub_parted1 FOR VALUES IN (1);
+
+-- fail - cannot add "only" partitioned table
+ALTER PUBLICATION testpub_forparted ADD TABLE ONLY testpub_parted;
+
+-- ok, should add partition testpub_parted1, sub-partition testpub_parted11
+ALTER PUBLICATION testpub_forparted ADD TABLE testpub_parted;
+\dRp+ testpub_forparted
+
+-- New partitions should automatically get added to the publications that
+-- the parent is in.
+CREATE TABLE testpub_parted2 PARTITION OF testpub_parted FOR VALUES IN (2);
+\dRp+ testpub_forparted
+
+-- Create a table that will later be attached to the parent and add it to
+-- the same publication as the one that the parent is in
+CREATE TABLE testpub_parted3 (LIKE testpub_parted);
+ALTER PUBLICATION testpub_forparted ADD TABLE testpub_parted3;
+\dRp+ testpub_forparted
+
+-- Attaching to parent should not result in the table being duplicatively
+-- added to the publication, nor an error
+ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted3 FOR VALUES IN (3);
+\dRp+ testpub_forparted
+
+-- cannot drop a partition from publication which parent is still part of
+ALTER PUBLICATION testpub_forparted DROP TABLE testpub_parted1;
+
+-- When the partition is detached, it and sub-partitions continue to be
+-- members of the publication
+ALTER TABLE testpub_parted DETACH PARTITION testpub_parted1;
+
+\dRp+ testpub_forparted
+
+-- sub-partition's membership is still inherited, so can't drop
+ALTER PUBLICATION testpub_forparted DROP TABLE testpub_parted11;
+
+-- dropping the now-detached partition should work though
+ALTER PUBLICATION testpub_forparted DROP TABLE testpub_parted1;
+
+\dRp+ testpub_forparted
+
+-- Some tests for SET TABLE
+
+-- no changes to the membership, because testpub_parted and testpub_parted3
+-- are already in the publication
+ALTER PUBLICATION testpub_forparted SET TABLE testpub_parted, testpub_parted3;
+
+\dRp+ testpub_forparted
+
+-- This should drop testpub_parted (hence, testpub_parted2, testpub_parted3)
+-- and add testpub_parted2, testpub_parted1 (hence testpub_parted11)
+ALTER PUBLICATION testpub_forparted SET TABLE testpub_parted2, testpub_parted1;
+\dRp+ testpub_forparted
+
+DROP PUBLICATION testpub_forparted;
+DROP TABLE testpub_parted, testpub_parted1;
ALTER PUBLICATION testpub_default ADD TABLE testpub_tbl1;
ALTER PUBLICATION testpub_default SET TABLE testpub_tbl1;
@@ -125,7 +198,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;
--
2.11.0