On Wed, Mar 18, 2026 at 3:31 PM Masahiko Sawada <[email protected]> wrote:
>
> On Wed, Mar 18, 2026 at 1:11 PM Jan Wieck <[email protected]> wrote:
> >
> > On 3/18/26 12:44, Masahiko Sawada wrote:
> > > On Wed, Mar 18, 2026 at 6:56 AM Amit Kapila <[email protected]> 
> > > wrote:
> > >>
> > >> On Tue, Mar 10, 2026 at 3:40 AM Masahiko Sawada <[email protected]> 
> > >> wrote:
> > >> >
> > >> > On Tue, Mar 3, 2026 at 2:22 AM Zhijie Hou (Fujitsu)
> > >> > <[email protected]> wrote:
> > >> > >
> > >> > > On Saturday, February 28, 2026 7:48 AM Masahiko Sawada 
> > >> > > <[email protected]> wrote:
> > >> > > > To: Marcos Pegoraro <[email protected]>
> > >> > > > Cc: PostgreSQL Hackers <[email protected]>
> > >> > > > Subject: Re: Initial COPY of Logical Replication is too slow
> > >> > > >
> > >> > > > Another variant of this approach is to extend
> > >> > > > pg_get_publication_table() so that it can accept a relid to get 
> > >> > > > the publication
> > >> > > > information of the specific table. I've attached the patch for 
> > >> > > > this idea. I'm
> > >> > > > going to add regression test cases.
> > >> > > >
> > >> > > > pg_get_publication_table() is a VARIACID array function so the 
> > >> > > > patch changes
> > >> > > > its signature to {text[] [, oid]}, breaking the tool 
> > >> > > > compatibility. Given this
> > >> > > > function is mostly an internal-use function (we don't have the 
> > >> > > > documentation
> > >> > > > for it), it would probably be okay with it. I find it's clearer 
> > >> > > > than the other
> > >> > > > approach of introducing pg_get_publication_table_info(). Feedback 
> > >> > > > is very
> > >> > > > welcome.
> > >> > >
> > >> > > Thanks for updating the patch.
> > >> > >
> > >> > > I have few comments for the function change:
> > >> > >
> > >> > > 1.
> > >> > >
> > >> > > If we change the function signature, will it affect use cases where 
> > >> > > the
> > >> > > publisher version is newer and the subscriber version is older ? 
> > >> > > E.g., when
> > >> > > publisher is passing text style publication name to 
> > >> > > pg_get_publication_tables().
> > >> >
> > >> > Good point.
> > >> >
> > >> > I noticed that changing the function signature of
> > >> > pg_get_publication_tables() breaks logical replication setups where
> > >> > the subscriber is 18 or older.
> > >> >
> > >>
> > >> Why adding a new function with additional parameters (Oid relid)
> > >> couldn't help with such a case? I am asking because your previous
> > >> version code looks simpler as compared to the new patch version.
> > >
> > > I tried to pass a relid to pg_get_publication_tables() but we cannot
> > > avoid changing its signature because it's a VARIADIC array function.
> > > The previous patch changed pg_get_publication_tables(VARIADIC text[])
> > > to pg_get_publication_tables(text[] {, relid}). However, changing the
> > > function signature would break the logical replication from v19 to an
> > > older version.
> >
> > Would it be possible to use function overloading to provide both
> > signatures handled by different C functions internally?
>
> Yes, we can define both pg_get_publication_tables(VARIADIC text[]) and
> pg_get_publication_tables(text, oid), which seems like a less invasive
> approach. I'll give this idea a shot and see how it goes.

I've attached the patch to implement this idea. The patch still
introduces a new function but it overloads
pg_get_publication_tables(). We might be able to handle different
input (array or text) in pg_get_publication_tables() better, but it's
enough for discussion at least.

Regards,

-- 
Masahiko Sawada
Amazon Web Services: https://aws.amazon.com
From c3647910dadb645895cea1ef15c11aaa6cd25d18 Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <[email protected]>
Date: Fri, 27 Feb 2026 15:42:38 -0800
Subject: [PATCH v3] Avoid full table scans when getting publication table
 information by tablesync workers.

Reported-by: Marcos Pegoraro <[email protected]>
Reviewed-by: Zhijie Hou (Fujitsu) <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/cab-jlwbbfnuasyenzwp0tck9unkthbzqi6woxnevut6+mv8...@mail.gmail.com
---
 src/backend/catalog/pg_publication.c        | 294 +++++++++++++++-----
 src/backend/replication/logical/tablesync.c |  74 +++--
 src/include/catalog/pg_proc.dat             |  11 +-
 src/test/regress/expected/publication.out   | 131 +++++++++
 src/test/regress/sql/publication.sql        |  69 +++++
 5 files changed, 490 insertions(+), 89 deletions(-)

diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index a79157c43bf..5f687491a4e 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -1208,12 +1208,129 @@ GetPublicationByName(const char *pubname, bool missing_ok)
 }
 
 /*
- * Get information of the tables in the given publication array.
+ * Returns true if the table of the given relid is published for the specified
+ * publication.
+ *
+ * This function evaluates the effective published OID based on the
+ * publish_via_partition_root setting, rather than just checking catalog entries
+ * (e.g., pg_publication_rel). For instance, when publish_via_partition_root is
+ * false, it returns false for a parent partitioned table and true for its leaf
+ * partitions, even if the parent is the one explicitly added to the publication.
+ *
+ * For performance reasons, this function avoids the overhead of constructing
+ * the complete list of published tables during the evaluation. It can execute
+ * quickly even when the publication contains a large number of relations.
+ */
+static bool
+is_table_publishable_in_publication(Oid relid, Publication *pub)
+{
+	if (pub->pubviaroot)
+	{
+		if (pub->alltables)
+		{
+			/*
+			 * ALL TABLE publications with pubviaroot=true include only tables
+			 * that are either regular tables or top-most partitioned tables.
+			 */
+			if (get_rel_relispartition(relid))
+				return false;
+
+			/*
+			 * Check if the table is specified in the EXCEPT clause in the
+			 * publication. ALL TABLE publications have pg_publication_rel
+			 * entries only for EXCEPT'ed tables, so it's sufficient to check
+			 * the existence of its entry.
+			 */
+			return !SearchSysCacheExists2(PUBLICATIONRELMAP,
+										  ObjectIdGetDatum(relid),
+										  ObjectIdGetDatum(pub->oid));
+		}
+
+		/*
+		 * Check if its corresponding entry exists either in
+		 * pg_publication_rel or pg_publication_namespace.
+		 */
+		return (SearchSysCacheExists2(PUBLICATIONRELMAP,
+									  ObjectIdGetDatum(relid),
+									  ObjectIdGetDatum(pub->oid)) ||
+				SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+									  ObjectIdGetDatum(get_rel_namespace(relid)),
+									  ObjectIdGetDatum(pub->oid)));
+	}
+
+	/*
+	 * For non-pubviaroot publications, partitioned table's OID can never be a
+	 * published OID.
+	 */
+	if (get_rel_relkind(relid) == RELKIND_PARTITIONED_TABLE)
+		return false;
+
+	if (pub->alltables)
+	{
+		Oid			target_relid = relid;
+
+		if (get_rel_relispartition(relid))
+		{
+			List	   *ancestors = get_partition_ancestors(relid);
+
+			/*
+			 * Only the top-most ancestor can appear in the EXCEPT clause.
+			 * Therefore, for a partition, exclusion must be evaluated at the
+			 * top-most ancestor.
+			 */
+			target_relid = llast_oid(ancestors);
+
+			list_free(ancestors);
+		}
+
+		/*
+		 * The table is published unless it's specified in the EXCEPT clause.
+		 * ALL TABLE publications have pg_publication_rel entries only for
+		 * EXCEPT'ed tables, so it's sufficient to check the existence of its
+		 * entry.
+		 */
+		return !SearchSysCacheExists2(PUBLICATIONRELMAP,
+									  ObjectIdGetDatum(target_relid),
+									  ObjectIdGetDatum(pub->oid));
+	}
+
+	if (get_rel_relispartition(relid))
+	{
+		List	   *ancestors = get_partition_ancestors(relid);
+		Oid			topmost = GetTopMostAncestorInPublication(pub->oid, ancestors,
+															  NULL);
+
+		list_free(ancestors);
+
+		/* This table is published if its ancestor is published */
+		if (OidIsValid(topmost))
+			return true;
+
+		/* The partition itself might be published, so check below */
+	}
+
+	return (SearchSysCacheExists2(PUBLICATIONRELMAP,
+								  ObjectIdGetDatum(relid),
+								  ObjectIdGetDatum(pub->oid)) ||
+			SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
+								  ObjectIdGetDatum(get_rel_namespace(relid)),
+								  ObjectIdGetDatum(pub->oid)));
+}
+
+/*
+ * Helper function to get information of the tables in the given
+ * publication(s).
+ *
+ * The parameters pubnames and {pubname, target_relid} are mutually exclusive.
+ * If target_relid is provided, the function returns information only for that
+ * specific table. Otherwise, if returns information for all tables within the
+ * specified publications.
  *
  * Returns pubid, relid, column list, row filter for each table.
  */
-Datum
-pg_get_publication_tables(PG_FUNCTION_ARGS)
+static Datum
+pg_get_publication_tables(FunctionCallInfo fcinfo, ArrayType *pubnames,
+						  text *pubname, Oid target_relid)
 {
 #define NUM_PUBLICATION_TABLES_ELEM	4
 	FuncCallContext *funcctx;
@@ -1224,11 +1341,6 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
 	{
 		TupleDesc	tupdesc;
 		MemoryContext oldcontext;
-		ArrayType  *arr;
-		Datum	   *elems;
-		int			nelems,
-					i;
-		bool		viaroot = false;
 
 		/* create a function context for cross-call persistence */
 		funcctx = SRF_FIRSTCALL_INIT();
@@ -1236,81 +1348,111 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
 		/* switch to memory context appropriate for multiple function calls */
 		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
 
-		/*
-		 * Deconstruct the parameter into elements where each element is a
-		 * publication name.
-		 */
-		arr = PG_GETARG_ARRAYTYPE_P(0);
-		deconstruct_array_builtin(arr, TEXTOID, &elems, NULL, &nelems);
-
-		/* Get Oids of tables from each publication. */
-		for (i = 0; i < nelems; i++)
+		if (pubname != NULL)
 		{
-			Publication *pub_elem;
-			List	   *pub_elem_tables = NIL;
-			ListCell   *lc;
+			Publication *pub;
 
-			pub_elem = GetPublicationByName(TextDatumGetCString(elems[i]), false);
+			Assert(OidIsValid(target_relid));
+			pub = GetPublicationByName(text_to_cstring(pubname), false);
 
-			/*
-			 * Publications support partitioned tables. If
-			 * publish_via_partition_root is false, all changes are replicated
-			 * using leaf partition identity and schema, so we only need
-			 * those. Otherwise, get the partitioned table itself.
-			 */
-			if (pub_elem->alltables)
-				pub_elem_tables = GetAllPublicationRelations(pub_elem->oid,
-															 RELKIND_RELATION,
-															 pub_elem->pubviaroot);
-			else
+			if (is_table_publishable_in_publication(target_relid, pub))
 			{
-				List	   *relids,
-						   *schemarelids;
-
-				relids = GetIncludedPublicationRelations(pub_elem->oid,
-														 pub_elem->pubviaroot ?
-														 PUBLICATION_PART_ROOT :
-														 PUBLICATION_PART_LEAF);
-				schemarelids = GetAllSchemaPublicationRelations(pub_elem->oid,
-																pub_elem->pubviaroot ?
-																PUBLICATION_PART_ROOT :
-																PUBLICATION_PART_LEAF);
-				pub_elem_tables = list_concat_unique_oid(relids, schemarelids);
+				published_rel *table_info = palloc_object(published_rel);
+
+				table_info->relid = target_relid;
+				table_info->pubid = pub->oid;
+				table_infos = lappend(table_infos, table_info);
 			}
+		}
+		else
+		{
+			Datum	   *elems;
+			int			nelems,
+						i;
+			bool		viaroot = false;
+
+			Assert(pubnames != NULL);
 
 			/*
-			 * Record the published table and the corresponding publication so
-			 * that we can get row filters and column lists later.
-			 *
-			 * When a table is published by multiple publications, to obtain
-			 * all row filters and column lists, the structure related to this
-			 * table will be recorded multiple times.
+			 * Deconstruct the parameter into elements where each element is a
+			 * publication name.
 			 */
-			foreach(lc, pub_elem_tables)
+			deconstruct_array_builtin(pubnames, TEXTOID, &elems, NULL, &nelems);
+
+			/* Get Oids of tables from each publication. */
+			for (i = 0; i < nelems; i++)
 			{
-				published_rel *table_info = palloc_object(published_rel);
+				Publication *pub_elem;
+				List	   *pub_elem_tables = NIL;
+				ListCell   *lc;
+
+				pub_elem = GetPublicationByName(TextDatumGetCString(elems[i]), false);
+
+				/*
+				 * Publications support partitioned tables. If
+				 * publish_via_partition_root is false, all changes are
+				 * replicated using leaf partition identity and schema, so we
+				 * only need those. Otherwise, get the partitioned table
+				 * itself.
+				 */
+				if (pub_elem->alltables)
+					pub_elem_tables = GetAllPublicationRelations(pub_elem->oid,
+																 RELKIND_RELATION,
+																 pub_elem->pubviaroot);
+				else
+				{
+					List	   *relids,
+							   *schemarelids;
+
+					relids = GetIncludedPublicationRelations(pub_elem->oid,
+															 pub_elem->pubviaroot ?
+															 PUBLICATION_PART_ROOT :
+															 PUBLICATION_PART_LEAF);
+					schemarelids = GetAllSchemaPublicationRelations(pub_elem->oid,
+																	pub_elem->pubviaroot ?
+																	PUBLICATION_PART_ROOT :
+																	PUBLICATION_PART_LEAF);
+					pub_elem_tables = list_concat_unique_oid(relids, schemarelids);
+				}
 
-				table_info->relid = lfirst_oid(lc);
-				table_info->pubid = pub_elem->oid;
-				table_infos = lappend(table_infos, table_info);
+				/*
+				 * Record the published table and the corresponding
+				 * publication so that we can get row filters and column lists
+				 * later.
+				 *
+				 * When a table is published by multiple publications, to
+				 * obtain all row filters and column lists, the structure
+				 * related to this table will be recorded multiple times.
+				 */
+				foreach(lc, pub_elem_tables)
+				{
+					published_rel *table_info = palloc_object(published_rel);
+
+					table_info->relid = lfirst_oid(lc);
+					table_info->pubid = pub_elem->oid;
+					table_infos = lappend(table_infos, table_info);
+				}
+
+				/*
+				 * At least one publication is using
+				 * publish_via_partition_root.
+				 */
+				if (pub_elem->pubviaroot)
+					viaroot = true;
 			}
 
-			/* At least one publication is using publish_via_partition_root. */
-			if (pub_elem->pubviaroot)
-				viaroot = true;
+			/*
+			 * 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 (viaroot)
+				filter_partitions(table_infos);
 		}
 
-		/*
-		 * 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 (viaroot)
-			filter_partitions(table_infos);
-
 		/* Construct a tuple descriptor for the result rows. */
 		tupdesc = CreateTemplateTupleDesc(NUM_PUBLICATION_TABLES_ELEM);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pubid",
@@ -1435,6 +1577,20 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
 	SRF_RETURN_DONE(funcctx);
 }
 
+Datum
+pg_get_publication_tables_a(PG_FUNCTION_ARGS)
+{
+	/* Get the information of the tables in the given publications */
+	return pg_get_publication_tables(fcinfo, PG_GETARG_ARRAYTYPE_P(0), NULL, InvalidOid);
+}
+
+Datum
+pg_get_publication_tables_b(PG_FUNCTION_ARGS)
+{
+	/* Get the information of the specified table in the given publication */
+	return pg_get_publication_tables(fcinfo, NULL, PG_GETARG_TEXT_P(0), PG_GETARG_OID(1));
+}
+
 /*
  * Returns Oids of sequences in a publication.
  */
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index f49a4852ecb..884b56bb26c 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -798,17 +798,37 @@ fetch_remote_table_info(char *nspname, char *relname, LogicalRepRelation *lrel,
 		 * publications).
 		 */
 		resetStringInfo(&cmd);
-		appendStringInfo(&cmd,
-						 "SELECT DISTINCT"
-						 "  (CASE WHEN (array_length(gpt.attrs, 1) = c.relnatts)"
-						 "   THEN NULL ELSE gpt.attrs END)"
-						 "  FROM pg_publication p,"
-						 "  LATERAL pg_get_publication_tables(p.pubname) gpt,"
-						 "  pg_class c"
-						 " WHERE gpt.relid = %u AND c.oid = gpt.relid"
-						 "   AND p.pubname IN ( %s )",
-						 lrel->remoteid,
-						 pub_names->data);
+
+		if (server_version >= 190000)
+		{
+			/*
+			 * We can pass relid to pg_get_publication_table_info() since
+			 * version 19.
+			 */
+			appendStringInfo(&cmd,
+							 "SELECT DISTINCT"
+							 "  (CASE WHEN (array_length(gpt.attrs, 1) = c.relnatts)"
+							 "   THEN NULL ELSE gpt.attrs END)"
+							 "  FROM pg_publication p,"
+							 "  LATERAL pg_get_publication_tables(p.pubname, %u) gpt,"
+							 "  pg_class c"
+							 " WHERE c.oid = gpt.relid"
+							 "   AND p.pubname IN ( %s )",
+							 lrel->remoteid,
+							 pub_names->data);
+		}
+		else
+			appendStringInfo(&cmd,
+							 "SELECT DISTINCT"
+							 "  (CASE WHEN (array_length(gpt.attrs, 1) = c.relnatts)"
+							 "   THEN NULL ELSE gpt.attrs END)"
+							 "  FROM pg_publication p,"
+							 "  LATERAL pg_get_publication_tables(p.pubname) gpt,"
+							 "  pg_class c"
+							 " WHERE gpt.relid = %u AND c.oid = gpt.relid"
+							 "   AND p.pubname IN ( %s )",
+							 lrel->remoteid,
+							 pub_names->data);
 
 		pubres = walrcv_exec(LogRepWorkerWalRcvConn, cmd.data,
 							 lengthof(attrsRow), attrsRow);
@@ -982,14 +1002,30 @@ fetch_remote_table_info(char *nspname, char *relname, LogicalRepRelation *lrel,
 
 		/* Check for row filters. */
 		resetStringInfo(&cmd);
-		appendStringInfo(&cmd,
-						 "SELECT DISTINCT pg_get_expr(gpt.qual, gpt.relid)"
-						 "  FROM pg_publication p,"
-						 "  LATERAL pg_get_publication_tables(p.pubname) gpt"
-						 " WHERE gpt.relid = %u"
-						 "   AND p.pubname IN ( %s )",
-						 lrel->remoteid,
-						 pub_names->data);
+
+		if (server_version >= 190000)
+		{
+			/*
+			 * We can pass relid to pg_get_publication_table_info() since
+			 * version 19.
+			 */
+			appendStringInfo(&cmd,
+							 "SELECT DISTINCT pg_get_expr(gpt.qual, gpt.relid)"
+							 "  FROM pg_publication p,"
+							 "  LATERAL pg_get_publication_tables(p.pubname, %u) gpt"
+							 " WHERE p.pubname IN ( %s )",
+							 lrel->remoteid,
+							 pub_names->data);
+		}
+		else
+			appendStringInfo(&cmd,
+							 "SELECT DISTINCT pg_get_expr(gpt.qual, gpt.relid)"
+							 "  FROM pg_publication p,"
+							 "  LATERAL pg_get_publication_tables(p.pubname) gpt"
+							 " WHERE gpt.relid = %u"
+							 "   AND p.pubname IN ( %s )",
+							 lrel->remoteid,
+							 pub_names->data);
 
 		res = walrcv_exec(LogRepWorkerWalRcvConn, cmd.data, 1, qualRow);
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fc8d82665b8..294ee717a6d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12453,7 +12453,16 @@
   proallargtypes => '{_text,oid,oid,int2vector,pg_node_tree}',
   proargmodes => '{v,o,o,o,o}',
   proargnames => '{pubname,pubid,relid,attrs,qual}',
-  prosrc => 'pg_get_publication_tables' },
+  prosrc => 'pg_get_publication_tables_a' },
+{ oid => '8060',
+  descr => 'get information of the specified table that is part of the specified publication',
+  proname => 'pg_get_publication_tables', prorows => '1',
+  proretset => 't', provolatile => 's',
+  prorettype => 'record', proargtypes => 'text oid',
+  proallargtypes => '{text,oid,oid,oid,int2vector,pg_node_tree}',
+  proargmodes => '{i,i,o,o,o,o}',
+  proargnames => '{pubname,relid,pubid,relid,attrs,qual}',
+  prosrc => 'pg_get_publication_tables_b' },
 { oid => '8052', descr => 'get OIDs of sequences in a publication',
   proname => 'pg_get_publication_sequences', prorows => '1000', proretset => 't',
   provolatile => 's', prorettype => 'oid', proargtypes => 'text',
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 681d2564ed5..91c339bd278 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -2182,6 +2182,137 @@ DROP TABLE testpub_merge_pk;
 RESET SESSION AUTHORIZATION;
 DROP ROLE regress_publication_user, regress_publication_user2;
 DROP ROLE regress_publication_user_dummy;
+-- Test pg_get_publication_tables(text, oid) function
+CREATE SCHEMA gpt_test_sch;
+CREATE TABLE gpt_test_sch.tbl_sch (id int);
+CREATE TABLE tbl_normal (id int);
+CREATE TABLE tbl_parent (id1 int, id2 int, id3 int) PARTITION BY RANGE (id1);
+CREATE TABLE tbl_part1 PARTITION OF tbl_parent FOR VALUES FROM (1) TO (10);
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION pub_all FOR ALL TABLES WITH (publish_via_partition_root = true);
+CREATE PUBLICATION pub_all_novia_root FOR ALL TABLES WITH (publish_via_partition_root = false);
+CREATE PUBLICATION pub_all_except FOR ALL TABLES EXCEPT TABLE (tbl_parent, gpt_test_sch.tbl_sch);
+CREATE PUBLICATION pub_schema FOR TABLES IN SCHEMA gpt_test_sch;
+CREATE PUBLICATION pub_normal FOR TABLE tbl_normal WHERE (id < 10);
+CREATE PUBLICATION pub_part_leaf FOR TABLE tbl_part1 WITH (publish_via_partition_root = false);
+CREATE PUBLICATION pub_part_parent FOR TABLE tbl_parent (id1, id2) WHERE (id1 = 10) WITH (publish_via_partition_root = true);
+CREATE PUBLICATION pub_part_parent_novia_root FOR TABLE tbl_parent WITH (publish_via_partition_root = false);
+RESET client_min_messages;
+CREATE FUNCTION test_gpt(pubname text, relname text)
+RETURNS TABLE (
+  pubname text,
+  relname name,
+  attrs text,
+  qual text
+)
+BEGIN ATOMIC
+  SELECT p.pubname, c.relname, gpt.attrs::text, pg_get_expr(gpt.qual, gpt.relid)
+    FROM pg_get_publication_tables(pubname, relname::regclass::oid) gpt
+    JOIN pg_publication p ON p.oid = gpt.pubid
+    JOIN pg_class c ON c.oid = gpt.relid
+  ORDER BY p.pubname, c.relname;
+END;
+SELECT * FROM test_gpt('pub_normal', 'tbl_normal');
+  pubname   |  relname   | attrs |   qual    
+------------+------------+-------+-----------
+ pub_normal | tbl_normal | 1     | (id < 10)
+(1 row)
+
+SELECT * FROM test_gpt('pub_schema', 'tbl_normal'); -- no result
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+SELECT * FROM test_gpt('pub_part_parent', 'tbl_parent');
+     pubname     |  relname   | attrs |    qual    
+-----------------+------------+-------+------------
+ pub_part_parent | tbl_parent | 1 2   | (id1 = 10)
+(1 row)
+
+SELECT * FROM test_gpt('pub_part_parent', 'tbl_part1'); -- no result
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+SELECT * FROM test_gpt('pub_part_parent_novia_root', 'tbl_parent'); -- no result
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+SELECT * FROM test_gpt('pub_part_parent_novia_root', 'tbl_part1');
+          pubname           |  relname  | attrs | qual 
+----------------------------+-----------+-------+------
+ pub_part_parent_novia_root | tbl_part1 | 1 2 3 | 
+(1 row)
+
+SELECT * FROM test_gpt('pub_part_leaf', 'tbl_parent'); -- no result
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+SELECT * FROM test_gpt('pub_part_leaf', 'tbl_part1');
+    pubname    |  relname  | attrs | qual 
+---------------+-----------+-------+------
+ pub_part_leaf | tbl_part1 | 1 2 3 | 
+(1 row)
+
+SELECT * FROM test_gpt('pub_all', 'tbl_parent');
+ pubname |  relname   | attrs | qual 
+---------+------------+-------+------
+ pub_all | tbl_parent | 1 2 3 | 
+(1 row)
+
+SELECT * FROM test_gpt('pub_all', 'tbl_part1'); -- no result
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+SELECT * FROM test_gpt('pub_all_except', 'tbl_normal');
+    pubname     |  relname   | attrs | qual 
+----------------+------------+-------+------
+ pub_all_except | tbl_normal | 1     | 
+(1 row)
+
+SELECT * FROM test_gpt('pub_all_except', 'gpt_test_sch.tbl_sch'); -- no result (excluded)
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+SELECT * FROM test_gpt('pub_all_except', 'tbl_parent'); -- no result (excluded)
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+SELECT * FROM test_gpt('pub_all_except', 'tbl_part1'); -- no result (excluded)
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+SELECT * FROM test_gpt('pub_all_novia_root', 'tbl_parent'); -- no result
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+SELECT * FROM test_gpt('pub_all_novia_root', 'tbl_part1');
+      pubname       |  relname  | attrs | qual 
+--------------------+-----------+-------+------
+ pub_all_novia_root | tbl_part1 | 1 2 3 | 
+(1 row)
+
+-- Clean up
+DROP FUNCTION test_gpt(text[], relname);
+ERROR:  type "relname" does not exist
+DROP PUBLICATION pub_all;
+DROP PUBLICATION pub_all_novia_root;
+DROP PUBLICATION pub_all_except;
+DROP PUBLICATION pub_schema;
+DROP PUBLICATION pub_normal;
+DROP PUBLICATION pub_part_leaf;
+DROP PUBLICATION pub_part_parent;
+DROP PUBLICATION pub_part_parent_novia_root;
+DROP TABLE tbl_normal, tbl_parent, tbl_part1;
+DROP SCHEMA gpt_test_sch CASCADE;
+NOTICE:  drop cascades to table gpt_test_sch.tbl_sch
 -- stage objects for pg_dump tests
 CREATE SCHEMA pubme CREATE TABLE t0 (c int, d int) CREATE TABLE t1 (c int);
 CREATE SCHEMA pubme2 CREATE TABLE t0 (c int, d int);
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 405579dad52..0f3f1400abe 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -1378,6 +1378,75 @@ RESET SESSION AUTHORIZATION;
 DROP ROLE regress_publication_user, regress_publication_user2;
 DROP ROLE regress_publication_user_dummy;
 
+-- Test pg_get_publication_tables(text, oid) function
+CREATE SCHEMA gpt_test_sch;
+CREATE TABLE gpt_test_sch.tbl_sch (id int);
+CREATE TABLE tbl_normal (id int);
+CREATE TABLE tbl_parent (id1 int, id2 int, id3 int) PARTITION BY RANGE (id1);
+CREATE TABLE tbl_part1 PARTITION OF tbl_parent FOR VALUES FROM (1) TO (10);
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION pub_all FOR ALL TABLES WITH (publish_via_partition_root = true);
+CREATE PUBLICATION pub_all_novia_root FOR ALL TABLES WITH (publish_via_partition_root = false);
+CREATE PUBLICATION pub_all_except FOR ALL TABLES EXCEPT TABLE (tbl_parent, gpt_test_sch.tbl_sch);
+CREATE PUBLICATION pub_schema FOR TABLES IN SCHEMA gpt_test_sch;
+CREATE PUBLICATION pub_normal FOR TABLE tbl_normal WHERE (id < 10);
+CREATE PUBLICATION pub_part_leaf FOR TABLE tbl_part1 WITH (publish_via_partition_root = false);
+CREATE PUBLICATION pub_part_parent FOR TABLE tbl_parent (id1, id2) WHERE (id1 = 10) WITH (publish_via_partition_root = true);
+CREATE PUBLICATION pub_part_parent_novia_root FOR TABLE tbl_parent WITH (publish_via_partition_root = false);
+RESET client_min_messages;
+
+CREATE FUNCTION test_gpt(pubname text, relname text)
+RETURNS TABLE (
+  pubname text,
+  relname name,
+  attrs text,
+  qual text
+)
+BEGIN ATOMIC
+  SELECT p.pubname, c.relname, gpt.attrs::text, pg_get_expr(gpt.qual, gpt.relid)
+    FROM pg_get_publication_tables(pubname, relname::regclass::oid) gpt
+    JOIN pg_publication p ON p.oid = gpt.pubid
+    JOIN pg_class c ON c.oid = gpt.relid
+  ORDER BY p.pubname, c.relname;
+END;
+
+SELECT * FROM test_gpt('pub_normal', 'tbl_normal');
+SELECT * FROM test_gpt('pub_schema', 'tbl_normal'); -- no result
+
+SELECT * FROM test_gpt('pub_part_parent', 'tbl_parent');
+SELECT * FROM test_gpt('pub_part_parent', 'tbl_part1'); -- no result
+
+SELECT * FROM test_gpt('pub_part_parent_novia_root', 'tbl_parent'); -- no result
+SELECT * FROM test_gpt('pub_part_parent_novia_root', 'tbl_part1');
+
+SELECT * FROM test_gpt('pub_part_leaf', 'tbl_parent'); -- no result
+SELECT * FROM test_gpt('pub_part_leaf', 'tbl_part1');
+
+SELECT * FROM test_gpt('pub_all', 'tbl_parent');
+SELECT * FROM test_gpt('pub_all', 'tbl_part1'); -- no result
+
+SELECT * FROM test_gpt('pub_all_except', 'tbl_normal');
+SELECT * FROM test_gpt('pub_all_except', 'gpt_test_sch.tbl_sch'); -- no result (excluded)
+SELECT * FROM test_gpt('pub_all_except', 'tbl_parent'); -- no result (excluded)
+SELECT * FROM test_gpt('pub_all_except', 'tbl_part1'); -- no result (excluded)
+
+SELECT * FROM test_gpt('pub_all_novia_root', 'tbl_parent'); -- no result
+SELECT * FROM test_gpt('pub_all_novia_root', 'tbl_part1');
+
+-- Clean up
+DROP FUNCTION test_gpt(text[], relname);
+DROP PUBLICATION pub_all;
+DROP PUBLICATION pub_all_novia_root;
+DROP PUBLICATION pub_all_except;
+DROP PUBLICATION pub_schema;
+DROP PUBLICATION pub_normal;
+DROP PUBLICATION pub_part_leaf;
+DROP PUBLICATION pub_part_parent;
+DROP PUBLICATION pub_part_parent_novia_root;
+DROP TABLE tbl_normal, tbl_parent, tbl_part1;
+DROP SCHEMA gpt_test_sch CASCADE;
+
 -- stage objects for pg_dump tests
 CREATE SCHEMA pubme CREATE TABLE t0 (c int, d int) CREATE TABLE t1 (c int);
 CREATE SCHEMA pubme2 CREATE TABLE t0 (c int, d int);
-- 
2.53.0

Reply via email to