]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Add target_relid parameter to pg_get_publication_tables().
authorMasahiko Sawada <msawada@postgresql.org>
Thu, 2 Apr 2026 18:34:50 +0000 (11:34 -0700)
committerMasahiko Sawada <msawada@postgresql.org>
Thu, 2 Apr 2026 18:34:50 +0000 (11:34 -0700)
When a tablesync worker checks whether a specific table is published,
it previously issued a query to the publisher calling
pg_get_publication_tables() and filtering the result by relid via a
WHERE clause. Because the function itself was fully evaluated before
the filter was applied, this forced the publisher to enumerate all
tables in the publication. For publications covering a large number of
tables, this resulted in expensive catalog scans and unnecessary CPU
overhead on the publisher.

This commit adds a new overloaded form of pg_get_publication_tables()
that accepts an array of publication names and a target table
OID. Instead of enumerating all published tables, it evaluates
membership for the specified relation via syscache lookups, using the
new is_table_publishable_in_publication() helper. This helper
correctly accounts for publish_via_partition_root, ALL TABLES with
EXCEPT clauses, schema publications, and partition inheritance, while
avoiding the overhead of building the complete published table list.

The existing VARIADIC array form of pg_get_publication_tables() is
preserved for backward compatibility. Tablesync workers use the new
two-argument form when connected to a publisher running PostgreSQL 19
or later.

Bump catalog version.

Reported-by: Marcos Pegoraro <marcos@f10.com.br>
Reviewed-by: Zhijie Hou <houzj.fnst@fujitsu.com>
Reviewed-by: Matheus Alcantara <matheusssilv97@gmail.com>
Reviewed-by: Amit Kapila <amit.kapila16@gmail.com>
Reviewed-by: Peter Smith <smithpb2250@gmail.com>
Reviewed-by: Hayato Kuroda <kuroda.hayato@fujitsu.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Reviewed-by: Haoyan Wang <wanghaoyan20@163.com>
Discussion: https://postgr.es/m/CAB-JLwbBFNuASyEnZWP0Tck9uNkthBZqi6WoXNevUT6+mV8XmA@mail.gmail.com

src/backend/catalog/pg_publication.c
src/backend/replication/logical/tablesync.c
src/include/catalog/catversion.h
src/include/catalog/pg_proc.dat
src/test/regress/expected/publication.out
src/test/regress/sql/publication.sql

index 82a22061d5be0fc872fb71b2d422bd8765cfa534..a43d385c60577f00507667062617933aabacada8 100644 (file)
@@ -163,6 +163,37 @@ is_publishable_relation(Relation rel)
        return is_publishable_class(RelationGetRelid(rel), rel->rd_rel);
 }
 
+/*
+ * Similar to is_publishable_class() but checks whether the given OID
+ * is a publishable "table" or not.
+ */
+static bool
+is_publishable_table(Oid tableoid)
+{
+       HeapTuple       tuple;
+       Form_pg_class relform;
+
+       tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(tableoid));
+       if (!HeapTupleIsValid(tuple))
+               return false;
+
+       relform = (Form_pg_class) GETSTRUCT(tuple);
+
+       /*
+        * is_publishable_class() includes sequences, so we need to explicitly
+        * check the relkind to filter them out here.
+        */
+       if (relform->relkind != RELKIND_SEQUENCE &&
+               is_publishable_class(tableoid, relform))
+       {
+               ReleaseSysCache(tuple);
+               return true;
+       }
+
+       ReleaseSysCache(tuple);
+       return false;
+}
+
 /*
  * SQL-callable variant of the above
  *
@@ -1264,12 +1295,116 @@ GetPublicationByName(const char *pubname, bool missing_ok)
 }
 
 /*
- * Get information of the tables in the given publication array.
+ * A helper function for pg_get_publication_tables() to check whether the
+ * table with the given relid is published in 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 returns 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.
  *
- * Returns pubid, relid, column list, row filter for each table.
+ * Note: this leaks memory for the ancestors list into the current memory
+ * context.
  */
-Datum
-pg_get_publication_tables(PG_FUNCTION_ARGS)
+static bool
+is_table_publishable_in_publication(Oid relid, Publication *pub)
+{
+       bool            relispartition;
+       List       *ancestors = NIL;
+
+       /*
+        * For non-pubviaroot publications, a partitioned table is never the
+        * effective published OID; only its leaf partitions can be.
+        */
+       if (!pub->pubviaroot && get_rel_relkind(relid) == RELKIND_PARTITIONED_TABLE)
+               return false;
+
+       relispartition = get_rel_relispartition(relid);
+
+       if (relispartition)
+               ancestors = get_partition_ancestors(relid);
+
+       if (pub->alltables)
+       {
+               /*
+                * ALL TABLES with pubviaroot includes only regular tables or top-most
+                * partitioned tables -- never child partitions.
+                */
+               if (pub->pubviaroot && relispartition)
+                       return false;
+
+               /*
+                * For ALL TABLES publications, the table is published unless it
+                * appears in the EXCEPT clause. Only the top-most can appear in the
+                * EXCEPT clause, so exclusion must be evaluated at the top-most
+                * ancestor if it has. These publications store only EXCEPT'ed tables
+                * in pg_publication_rel, so checking existence is sufficient.
+                *
+                * Note that this existence check below would incorrectly return true
+                * (published) for partitions when pubviaroot is enabled; however,
+                * that case is already caught and returned false by the above check.
+                */
+               return !SearchSysCacheExists2(PUBLICATIONRELMAP,
+                                                                         ObjectIdGetDatum(ancestors
+                                                                                                          ? llast_oid(ancestors) : relid),
+                                                                         ObjectIdGetDatum(pub->oid));
+       }
+
+       /*
+        * Non-ALL-TABLE publication cases.
+        *
+        * A table is published if it (or a containing schema) was explicitly
+        * added, or if it is a partition whose ancestor was added.
+        */
+
+       /*
+        * If an ancestor is published, the partition's status depends on
+        * publish_via_partition_root value.
+        *
+        * If it's true, the ancestor's relation OID is the effective published
+        * OID, so the partition itself should be excluded (return false).
+        *
+        * If it's false, the partition is covered by its ancestor's presence in
+        * the publication, it should be included (return true).
+        */
+       if (relispartition &&
+               OidIsValid(GetTopMostAncestorInPublication(pub->oid, ancestors, NULL)))
+               return !pub->pubviaroot;
+
+       /*
+        * Check whether the table is explicitly published via 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)));
+}
+
+/*
+ * Helper function to get information of the tables in the given
+ * publication(s).
+ *
+ * If filter_by_relid is true, only the row(s) for target_relid is returned;
+ * if target_relid does not exist or is not part of the publications, zero
+ * rows are returned.  If filter_by_relid is false, rows for all tables
+ * within the specified publications are returned and target_relid is
+ * ignored.
+ *
+ * Returns pubid, relid, column list, and row filter for each table.
+ */
+static Datum
+pg_get_publication_tables(FunctionCallInfo fcinfo, ArrayType *pubnames,
+                                                 Oid target_relid, bool filter_by_relid,
+                                                 bool pub_missing_ok)
 {
 #define NUM_PUBLICATION_TABLES_ELEM    4
        FuncCallContext *funcctx;
@@ -1280,7 +1415,6 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
        {
                TupleDesc       tupdesc;
                MemoryContext oldcontext;
-               ArrayType  *arr;
                Datum      *elems;
                int                     nelems,
                                        i;
@@ -1289,6 +1423,14 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
                /* create a function context for cross-call persistence */
                funcctx = SRF_FIRSTCALL_INIT();
 
+               /*
+                * Preliminary check if the specified table can be published in the
+                * first place. If not, we can return early without checking the given
+                * publications and the table.
+                */
+               if (filter_by_relid && !is_publishable_table(target_relid))
+                       SRF_RETURN_DONE(funcctx);
+
                /* switch to memory context appropriate for multiple function calls */
                oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
 
@@ -1296,8 +1438,7 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
                 * 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);
+               deconstruct_array_builtin(pubnames, TEXTOID, &elems, NULL, &nelems);
 
                /* Get Oids of tables from each publication. */
                for (i = 0; i < nelems; i++)
@@ -1306,32 +1447,48 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
                        List       *pub_elem_tables = NIL;
                        ListCell   *lc;
 
-                       pub_elem = GetPublicationByName(TextDatumGetCString(elems[i]), false);
+                       pub_elem = GetPublicationByName(TextDatumGetCString(elems[i]),
+                                                                                       pub_missing_ok);
 
-                       /*
-                        * 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);
+                       if (pub_elem == NULL)
+                               continue;
+
+                       if (filter_by_relid)
+                       {
+                               /* Check if the given table is published for the publication */
+                               if (is_table_publishable_in_publication(target_relid, pub_elem))
+                               {
+                                       pub_elem_tables = list_make1_oid(target_relid);
+                               }
+                       }
                        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);
+                               /*
+                                * 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);
+                               }
                        }
 
                        /*
@@ -1491,6 +1648,30 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
        SRF_RETURN_DONE(funcctx);
 }
 
+Datum
+pg_get_publication_tables_a(PG_FUNCTION_ARGS)
+{
+       /*
+        * Get information for all tables in the given publications.
+        * filter_by_relid is false so all tables are returned; pub_missing_ok is
+        * false for backward compatibility.
+        */
+       return pg_get_publication_tables(fcinfo, PG_GETARG_ARRAYTYPE_P(0),
+                                                                        InvalidOid, false, false);
+}
+
+Datum
+pg_get_publication_tables_b(PG_FUNCTION_ARGS)
+{
+       /*
+        * Get information for the specified table in the given publications. The
+        * SQL-level function is declared STRICT, so target_relid is guaranteed to
+        * be non-NULL here.
+        */
+       return pg_get_publication_tables(fcinfo, PG_GETARG_ARRAYTYPE_P(0),
+                                                                        PG_GETARG_OID(1), true, true);
+}
+
 /*
  * Returns Oids of sequences in a publication.
  */
index f49a4852ecb3054d0cd5f966464d122c7cf89cf8..eb7181142970026459e36c4f64b31f4c2cc51fee 100644 (file)
@@ -798,17 +798,35 @@ 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 both publication names and relid to
+                        * pg_get_publication_tables() since version 19.
+                        */
+                       appendStringInfo(&cmd,
+                                                        "SELECT DISTINCT"
+                                                        "  (CASE WHEN (array_length(gpt.attrs, 1) = c.relnatts)"
+                                                        "   THEN NULL ELSE gpt.attrs END)"
+                                                        "  FROM pg_get_publication_tables(ARRAY[%s], %u) gpt,"
+                                                        "  pg_class c"
+                                                        " WHERE c.oid = gpt.relid",
+                                                        pub_names->data,
+                                                        lrel->remoteid);
+               }
+               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 +1000,28 @@ 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 both publication names and relid to
+                        * pg_get_publication_tables() since version 19.
+                        */
+                       appendStringInfo(&cmd,
+                                                        "SELECT DISTINCT pg_get_expr(gpt.qual, gpt.relid)"
+                                                        "  FROM pg_get_publication_tables(ARRAY[%s], %u) gpt",
+                                                        pub_names->data,
+                                                        lrel->remoteid);
+               }
+               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);
 
index 81b2bf39b3f80b1a54f6419948e0f811dd3b5847..b1c5afc15df85aadea0f70600ba91650be7c9416 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     202603301
+#define CATALOG_VERSION_NO     202604021
 
 #endif
index 3579cec5744cba63b140ba4af80c331c4df2ffd1..acf16254b21bf41a45832274e12b575a05af7509 100644 (file)
   prorettype => 'record', proargtypes => '_text',
   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' },
+  proargnames => '{pubnames,pubid,relid,attrs,qual}',
+  prosrc => 'pg_get_publication_tables_a' },
+{ oid => '8060',
+  descr => 'get information of the specified table that is part of the specified publications',
+  proname => 'pg_get_publication_tables', prorows => '10',
+  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 => '{pubnames,target_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',
index d2aa9d45e4a8e2fb3b41dd96ebc6373a6b45d3d8..a9059a391386fa9dfdaf95cdd2056f6e74b7cbee 100644 (file)
@@ -2292,6 +2292,231 @@ 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);
+CREATE VIEW gpt_test_view AS SELECT * FROM tbl_normal;
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION pub_all FOR ALL TABLES WITH (publish_via_partition_root = true);
+CREATE PUBLICATION pub_all_no_viaroot 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) WITH (publish_via_partition_root = true);
+CREATE PUBLICATION pub_all_except_no_viaroot FOR ALL TABLES EXCEPT (TABLE tbl_parent, gpt_test_sch.tbl_sch) WITH (publish_via_partition_root = false);
+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_no_viaroot FOR TABLE tbl_parent WITH (publish_via_partition_root = false);
+CREATE PUBLICATION pub_part_parent_child FOR TABLE tbl_parent, tbl_part1 WITH (publish_via_partition_root = true);
+RESET client_min_messages;
+CREATE FUNCTION test_gpt(pubnames 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(pubnames, 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(ARRAY['pub_normal'], 'tbl_normal');
+  pubname   |  relname   | attrs |   qual    
+------------+------------+-------+-----------
+ pub_normal | tbl_normal | 1     | (id < 10)
+(1 row)
+
+SELECT * FROM test_gpt(ARRAY['pub_normal'], 'gpt_test_sch.tbl_sch'); -- no result
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+SELECT * FROM test_gpt(ARRAY['pub_schema'], 'gpt_test_sch.tbl_sch');
+  pubname   | relname | attrs | qual 
+------------+---------+-------+------
+ pub_schema | tbl_sch | 1     | 
+(1 row)
+
+SELECT * FROM test_gpt(ARRAY['pub_schema'], 'tbl_normal'); -- no result
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+SELECT * FROM test_gpt(ARRAY['pub_part_parent'], 'tbl_parent');
+     pubname     |  relname   | attrs |    qual    
+-----------------+------------+-------+------------
+ pub_part_parent | tbl_parent | 1 2   | (id1 = 10)
+(1 row)
+
+SELECT * FROM test_gpt(ARRAY['pub_part_parent'], 'tbl_part1'); -- no result
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+SELECT * FROM test_gpt(ARRAY['pub_part_parent_no_viaroot'], 'tbl_part1');
+          pubname           |  relname  | attrs | qual 
+----------------------------+-----------+-------+------
+ pub_part_parent_no_viaroot | tbl_part1 | 1 2 3 | 
+(1 row)
+
+SELECT * FROM test_gpt(ARRAY['pub_part_parent_no_viaroot'], 'tbl_parent'); -- no result
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+SELECT * FROM test_gpt(ARRAY['pub_part_leaf'], 'tbl_part1');
+    pubname    |  relname  | attrs | qual 
+---------------+-----------+-------+------
+ pub_part_leaf | tbl_part1 | 1 2 3 | 
+(1 row)
+
+SELECT * FROM test_gpt(ARRAY['pub_part_leaf'], 'tbl_parent'); -- no result
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+SELECT * FROM test_gpt(ARRAY['pub_all'], 'tbl_parent');
+ pubname |  relname   | attrs | qual 
+---------+------------+-------+------
+ pub_all | tbl_parent | 1 2 3 | 
+(1 row)
+
+SELECT * FROM test_gpt(ARRAY['pub_all'], 'tbl_part1'); -- no result
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+SELECT * FROM test_gpt(ARRAY['pub_all_no_viaroot'], 'tbl_part1');
+      pubname       |  relname  | attrs | qual 
+--------------------+-----------+-------+------
+ pub_all_no_viaroot | tbl_part1 | 1 2 3 | 
+(1 row)
+
+SELECT * FROM test_gpt(ARRAY['pub_all_no_viaroot'], 'tbl_parent'); -- no result
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+SELECT * FROM test_gpt(ARRAY['pub_part_parent_child'], 'tbl_parent');
+        pubname        |  relname   | attrs | qual 
+-----------------------+------------+-------+------
+ pub_part_parent_child | tbl_parent | 1 2 3 | 
+(1 row)
+
+SELECT * FROM test_gpt(ARRAY['pub_part_parent_child'], 'tbl_part1'); -- no result
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+-- test for the EXCEPT clause
+SELECT * FROM test_gpt(ARRAY['pub_all_except'], 'tbl_normal');
+    pubname     |  relname   | attrs | qual 
+----------------+------------+-------+------
+ pub_all_except | tbl_normal | 1     | 
+(1 row)
+
+SELECT * FROM test_gpt(ARRAY['pub_all_except'], 'gpt_test_sch.tbl_sch'); -- no result (excluded)
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+SELECT * FROM test_gpt(ARRAY['pub_all_except'], 'tbl_parent'); -- no result (excluded)
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+SELECT * FROM test_gpt(ARRAY['pub_all_except'], 'tbl_part1'); -- no result
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+SELECT * FROM test_gpt(ARRAY['pub_all_except_no_viaroot'], 'tbl_normal');
+          pubname          |  relname   | attrs | qual 
+---------------------------+------------+-------+------
+ pub_all_except_no_viaroot | tbl_normal | 1     | 
+(1 row)
+
+SELECT * FROM test_gpt(ARRAY['pub_all_except_no_viaroot'], 'gpt_test_sch.tbl_sch'); -- no result (excluded)
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+SELECT * FROM test_gpt(ARRAY['pub_all_except_no_viaroot'], 'tbl_parent'); -- no result (excluded)
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+SELECT * FROM test_gpt(ARRAY['pub_all_except_no_viaroot'], 'tbl_part1'); -- no result
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+-- two rows with different row filter
+SELECT * FROM test_gpt(ARRAY['pub_all', 'pub_normal'], 'tbl_normal');
+  pubname   |  relname   | attrs |   qual    
+------------+------------+-------+-----------
+ pub_all    | tbl_normal | 1     | 
+ pub_normal | tbl_normal | 1     | (id < 10)
+(2 rows)
+
+-- one row with 'pub_part_parent'
+SELECT * FROM test_gpt(ARRAY['pub_part_parent', 'pub_part_parent_no_viaroot'], 'tbl_parent');
+     pubname     |  relname   | attrs |    qual    
+-----------------+------------+-------+------------
+ pub_part_parent | tbl_parent | 1 2   | (id1 = 10)
+(1 row)
+
+-- no result, tbl_parent is the effective published OID due to pubviaroot
+SELECT * FROM test_gpt(ARRAY['pub_part_parent', 'pub_all'], 'tbl_part1');
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+-- no result, non-existent publication
+SELECT * FROM test_gpt(ARRAY['no_such_pub'], 'tbl_normal');
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+-- no result, non-table object
+SELECT * FROM test_gpt(ARRAY['pub_all'], 'gpt_test_view');
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+-- no result, empty publication array
+SELECT * FROM test_gpt(ARRAY[]::text[], 'tbl_normal');
+ pubname | relname | attrs | qual 
+---------+---------+-------+------
+(0 rows)
+
+-- no result, OID 0 as target_relid
+SELECT * FROM pg_get_publication_tables(ARRAY['pub_normal'], 0::oid);
+ pubid | relid | attrs | qual 
+-------+-------+-------+------
+(0 rows)
+
+-- Clean up
+DROP FUNCTION test_gpt(text[], text);
+DROP PUBLICATION pub_all;
+DROP PUBLICATION pub_all_no_viaroot;
+DROP PUBLICATION pub_all_except;
+DROP PUBLICATION pub_all_except_no_viaroot;
+DROP PUBLICATION pub_schema;
+DROP PUBLICATION pub_normal;
+DROP PUBLICATION pub_part_leaf;
+DROP PUBLICATION pub_part_parent;
+DROP PUBLICATION pub_part_parent_no_viaroot;
+DROP PUBLICATION pub_part_parent_child;
+DROP VIEW gpt_test_view;
+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);
index 6bafad27571195e90912583677e3e8700e884a8b..642e32fa098dae52de0db0856efe921932f690b8 100644 (file)
@@ -1438,6 +1438,113 @@ 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);
+CREATE VIEW gpt_test_view AS SELECT * FROM tbl_normal;
+
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION pub_all FOR ALL TABLES WITH (publish_via_partition_root = true);
+CREATE PUBLICATION pub_all_no_viaroot 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) WITH (publish_via_partition_root = true);
+CREATE PUBLICATION pub_all_except_no_viaroot FOR ALL TABLES EXCEPT (TABLE tbl_parent, gpt_test_sch.tbl_sch) WITH (publish_via_partition_root = false);
+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_no_viaroot FOR TABLE tbl_parent WITH (publish_via_partition_root = false);
+CREATE PUBLICATION pub_part_parent_child FOR TABLE tbl_parent, tbl_part1 WITH (publish_via_partition_root = true);
+RESET client_min_messages;
+
+CREATE FUNCTION test_gpt(pubnames 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(pubnames, 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(ARRAY['pub_normal'], 'tbl_normal');
+SELECT * FROM test_gpt(ARRAY['pub_normal'], 'gpt_test_sch.tbl_sch'); -- no result
+
+SELECT * FROM test_gpt(ARRAY['pub_schema'], 'gpt_test_sch.tbl_sch');
+SELECT * FROM test_gpt(ARRAY['pub_schema'], 'tbl_normal'); -- no result
+
+SELECT * FROM test_gpt(ARRAY['pub_part_parent'], 'tbl_parent');
+SELECT * FROM test_gpt(ARRAY['pub_part_parent'], 'tbl_part1'); -- no result
+
+SELECT * FROM test_gpt(ARRAY['pub_part_parent_no_viaroot'], 'tbl_part1');
+SELECT * FROM test_gpt(ARRAY['pub_part_parent_no_viaroot'], 'tbl_parent'); -- no result
+
+SELECT * FROM test_gpt(ARRAY['pub_part_leaf'], 'tbl_part1');
+SELECT * FROM test_gpt(ARRAY['pub_part_leaf'], 'tbl_parent'); -- no result
+
+SELECT * FROM test_gpt(ARRAY['pub_all'], 'tbl_parent');
+SELECT * FROM test_gpt(ARRAY['pub_all'], 'tbl_part1'); -- no result
+
+SELECT * FROM test_gpt(ARRAY['pub_all_no_viaroot'], 'tbl_part1');
+SELECT * FROM test_gpt(ARRAY['pub_all_no_viaroot'], 'tbl_parent'); -- no result
+
+SELECT * FROM test_gpt(ARRAY['pub_part_parent_child'], 'tbl_parent');
+SELECT * FROM test_gpt(ARRAY['pub_part_parent_child'], 'tbl_part1'); -- no result
+
+-- test for the EXCEPT clause
+SELECT * FROM test_gpt(ARRAY['pub_all_except'], 'tbl_normal');
+SELECT * FROM test_gpt(ARRAY['pub_all_except'], 'gpt_test_sch.tbl_sch'); -- no result (excluded)
+SELECT * FROM test_gpt(ARRAY['pub_all_except'], 'tbl_parent'); -- no result (excluded)
+SELECT * FROM test_gpt(ARRAY['pub_all_except'], 'tbl_part1'); -- no result
+SELECT * FROM test_gpt(ARRAY['pub_all_except_no_viaroot'], 'tbl_normal');
+SELECT * FROM test_gpt(ARRAY['pub_all_except_no_viaroot'], 'gpt_test_sch.tbl_sch'); -- no result (excluded)
+SELECT * FROM test_gpt(ARRAY['pub_all_except_no_viaroot'], 'tbl_parent'); -- no result (excluded)
+SELECT * FROM test_gpt(ARRAY['pub_all_except_no_viaroot'], 'tbl_part1'); -- no result
+
+-- two rows with different row filter
+SELECT * FROM test_gpt(ARRAY['pub_all', 'pub_normal'], 'tbl_normal');
+
+-- one row with 'pub_part_parent'
+SELECT * FROM test_gpt(ARRAY['pub_part_parent', 'pub_part_parent_no_viaroot'], 'tbl_parent');
+
+-- no result, tbl_parent is the effective published OID due to pubviaroot
+SELECT * FROM test_gpt(ARRAY['pub_part_parent', 'pub_all'], 'tbl_part1');
+
+-- no result, non-existent publication
+SELECT * FROM test_gpt(ARRAY['no_such_pub'], 'tbl_normal');
+
+-- no result, non-table object
+SELECT * FROM test_gpt(ARRAY['pub_all'], 'gpt_test_view');
+
+-- no result, empty publication array
+SELECT * FROM test_gpt(ARRAY[]::text[], 'tbl_normal');
+
+-- no result, OID 0 as target_relid
+SELECT * FROM pg_get_publication_tables(ARRAY['pub_normal'], 0::oid);
+
+-- Clean up
+DROP FUNCTION test_gpt(text[], text);
+DROP PUBLICATION pub_all;
+DROP PUBLICATION pub_all_no_viaroot;
+DROP PUBLICATION pub_all_except;
+DROP PUBLICATION pub_all_except_no_viaroot;
+DROP PUBLICATION pub_schema;
+DROP PUBLICATION pub_normal;
+DROP PUBLICATION pub_part_leaf;
+DROP PUBLICATION pub_part_parent;
+DROP PUBLICATION pub_part_parent_no_viaroot;
+DROP PUBLICATION pub_part_parent_child;
+DROP VIEW gpt_test_view;
+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);