From c7236930836a2b115c72030eb718634960a2b3b9 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Thu, 2 Dec 2021 14:03:28 +0800
Subject: [PATCH] Fix double publish of child table's data.

We publish the child table data twice for publication that has both
child and parent tables and is published with publish_via_partition_root
as true. This happens because subscribers will initiate synchronization
using both parent and child table as it gets both as separate tables
in the initial table list.

Ensure that pg_publication_tables returns only parent tables in such
cases.

Author: Hou Zhijie
Reviewed-by: Amit Kapila, Greg Nancarrow
Discussion: https://postgr.es/m/OS0PR01MB57167F45D481F78CDC5986F794B99@OS0PR01MB5716.jpnprd01.prod.outlook.com
Backpatch-through: 13
---
 doc/src/sgml/catalogs.sgml           | 10 +++++++
 src/backend/catalog/pg_publication.c | 52 ++++++++++++++++++++++++++++++++++++
 2 files changed, 62 insertions(+)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 688e778..8a709b6 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -11279,6 +11279,16 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
    publications there will be a row for each eligible table.
   </para>
 
+  <para>
+   For publications of partitioned tables with
+   <literal>publish_via_partition_root</literal> set to
+   <literal>true</literal>, only the partitioned table (and not its partitions)
+   is included in the view, whereas if
+   <literal>publish_via_partition_root</literal> is set to
+   <literal>false</literal>, only the individual partitions are included in the
+   view.
+  </para>
+
   <table>
    <title><structname>pg_publication_tables</structname> Columns</title>
    <tgroup cols="1">
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index b144a3b..3660911 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -106,6 +106,45 @@ is_publishable_class(Oid relid, Form_pg_class reltuple)
 }
 
 /*
+ * 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)
+		{
+			Oid			ancestor = lfirst_oid(lc2);
+
+			/* Check if the parent table exists in the published table list. */
+			if (list_member_oid(relids, ancestor))
+			{
+				skip = true;
+				break;
+			}
+		}
+
+		if (!skip)
+			result = lappend_oid(result, relid);
+	}
+
+	return result;
+}
+
+/*
  * Another variant of this, taking a Relation.
  */
 bool
@@ -557,10 +596,23 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
 		if (publication->alltables)
 			tables = GetAllTablesPublicationRelations(publication->pubviaroot);
 		else
+		{
 			tables = GetPublicationRelations(publication->oid,
 											 publication->pubviaroot ?
 											 PUBLICATION_PART_ROOT :
 											 PUBLICATION_PART_LEAF);
+
+			/*
+			 * 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);
-- 
2.7.2.windows.1

