Add target_relid parameter to pg_get_publication_tables().

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
This commit is contained in:
Masahiko Sawada 2026-04-02 11:34:50 -07:00
parent bc30c704ad
commit fd7a25af11
6 changed files with 605 additions and 51 deletions

View 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.
*
* Returns pubid, relid, column list, row filter for each table.
* 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.
*
* 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;
/*
* 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);
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.
*/

View 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);

View file

@ -57,6 +57,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 202603301
#define CATALOG_VERSION_NO 202604021
#endif

View file

@ -12467,8 +12467,17 @@
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',

View 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);

View 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);