From 7ffa55e77413743b63092a824c1a70f74dd122f0 Mon Sep 17 00:00:00 2001 From: Masahiko Sawada Date: Fri, 27 Feb 2026 15:42:38 -0800 Subject: [PATCH v2] Avoid full table scans when getting publication table information by tablesync workers. Reported-by: Marcos Pegoraro Reviewed-by: Zhijie Hou (Fujitsu) Reviewed-by: Matheus Alcantara Reviewed-by: Chao Li Discussion: https://postgr.es/m/CAB-JLwbBFNuASyEnZWP0Tck9uNkthBZqi6WoXNevUT6+mV8XmA@mail.gmail.com --- src/backend/catalog/pg_publication.c | 382 +++++++++++++++----- src/backend/replication/logical/tablesync.c | 68 +++- src/include/catalog/pg_proc.dat | 9 + src/test/regress/expected/publication.out | 129 +++++++ src/test/regress/sql/publication.sql | 67 ++++ 5 files changed, 543 insertions(+), 112 deletions(-) diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c index aadc7c202c6..5213f1d0a23 100644 --- a/src/backend/catalog/pg_publication.c +++ b/src/backend/catalog/pg_publication.c @@ -1207,6 +1207,240 @@ GetPublicationByName(const char *pubname, bool missing_ok) return OidIsValid(oid) ? GetPublication(oid) : NULL; } +/* + * 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))); +} + +/* + * pg_get_publication_tables() and pg_get_publication_table_info() use + * the same record type. + */ +#define NUM_PUBLICATION_TABLES_ELEM 4 + +/* + * Construct a tuple descriptor for both pg_get_publication_tales() and + * pg_get_publication_table_info() functions. + */ +static TupleDesc +create_published_rel_tuple_desc(void) +{ + TupleDesc tupdesc; + + tupdesc = CreateTemplateTupleDesc(NUM_PUBLICATION_TABLES_ELEM); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pubid", + OIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "relid", + OIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "attrs", + INT2VECTOROID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "qual", + PG_NODE_TREEOID, -1, 0); + + return BlessTupleDesc(tupdesc); +} + +/* + * Common routine for pg_get_publication_tables() and + * pg_get_publication_table_info() to construct the result tuple. + * tuple_desc should be the tuple description returned by + * create_published_rel_tuple_desc(). + */ +static HeapTuple +construct_published_rel_tuple(published_rel *table_info, TupleDesc tuple_desc) +{ + Publication *pub; + Oid relid = table_info->relid; + Oid schemaid = get_rel_namespace(relid); + HeapTuple pubtuple = NULL; + Datum values[NUM_PUBLICATION_TABLES_ELEM] = {0}; + bool nulls[NUM_PUBLICATION_TABLES_ELEM] = {0}; + + pub = GetPublication(table_info->pubid); + + values[0] = ObjectIdGetDatum(pub->oid); + values[1] = ObjectIdGetDatum(relid); + + /* + * We don't consider row filters or column lists for FOR ALL TABLES or FOR + * TABLES IN SCHEMA publications. + */ + if (!pub->alltables && + !SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP, + ObjectIdGetDatum(schemaid), + ObjectIdGetDatum(pub->oid))) + pubtuple = SearchSysCacheCopy2(PUBLICATIONRELMAP, + ObjectIdGetDatum(relid), + ObjectIdGetDatum(pub->oid)); + + if (HeapTupleIsValid(pubtuple)) + { + /* Lookup the column list attribute. */ + values[2] = SysCacheGetAttr(PUBLICATIONRELMAP, pubtuple, + Anum_pg_publication_rel_prattrs, + &(nulls[2])); + + /* Null indicates no filter. */ + values[3] = SysCacheGetAttr(PUBLICATIONRELMAP, pubtuple, + Anum_pg_publication_rel_prqual, + &(nulls[3])); + } + else + { + nulls[2] = true; + nulls[3] = true; + } + + /* Show all columns when the column list is not specified. */ + if (nulls[2]) + { + Relation rel = table_open(relid, AccessShareLock); + int nattnums = 0; + int16 *attnums; + TupleDesc desc = RelationGetDescr(rel); + + attnums = palloc_array(int16, desc->natts); + + for (int i = 0; i < desc->natts; i++) + { + Form_pg_attribute att = TupleDescAttr(desc, i); + + if (att->attisdropped) + continue; + + if (att->attgenerated) + { + /* We only support replication of STORED generated cols. */ + if (att->attgenerated != ATTRIBUTE_GENERATED_STORED) + continue; + + /* + * User hasn't requested to replicate STORED generated cols. + */ + if (pub->pubgencols_type != PUBLISH_GENCOLS_STORED) + continue; + } + + attnums[nattnums++] = att->attnum; + } + + if (nattnums > 0) + { + values[2] = PointerGetDatum(buildint2vector(attnums, nattnums)); + nulls[2] = false; + } + + table_close(rel, AccessShareLock); + } + + return heap_form_tuple(tuple_desc, values, nulls); +} + /* * Get information of the tables in the given publication array. * @@ -1215,14 +1449,12 @@ GetPublicationByName(const char *pubname, bool missing_ok) Datum pg_get_publication_tables(PG_FUNCTION_ARGS) { -#define NUM_PUBLICATION_TABLES_ELEM 4 FuncCallContext *funcctx; List *table_infos = NIL; /* stuff done only on the first call of the function */ if (SRF_IS_FIRSTCALL()) { - TupleDesc tupdesc; MemoryContext oldcontext; ArrayType *arr; Datum *elems; @@ -1311,18 +1543,7 @@ pg_get_publication_tables(PG_FUNCTION_ARGS) 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", - OIDOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 2, "relid", - OIDOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 3, "attrs", - INT2VECTOROID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 4, "qual", - PG_NODE_TREEOID, -1, 0); - - funcctx->tuple_desc = BlessTupleDesc(tupdesc); + funcctx->tuple_desc = create_published_rel_tuple_desc(); funcctx->user_fctx = table_infos; MemoryContextSwitchTo(oldcontext); @@ -1334,99 +1555,74 @@ pg_get_publication_tables(PG_FUNCTION_ARGS) if (funcctx->call_cntr < list_length(table_infos)) { - HeapTuple pubtuple = NULL; HeapTuple rettuple; - Publication *pub; published_rel *table_info = (published_rel *) list_nth(table_infos, funcctx->call_cntr); - Oid relid = table_info->relid; - Oid schemaid = get_rel_namespace(relid); - Datum values[NUM_PUBLICATION_TABLES_ELEM] = {0}; - bool nulls[NUM_PUBLICATION_TABLES_ELEM] = {0}; - - /* - * Form tuple with appropriate data. - */ - pub = GetPublication(table_info->pubid); + rettuple = construct_published_rel_tuple(table_info, funcctx->tuple_desc); - values[0] = ObjectIdGetDatum(pub->oid); - values[1] = ObjectIdGetDatum(relid); + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(rettuple)); + } - /* - * We don't consider row filters or column lists for FOR ALL TABLES or - * FOR TABLES IN SCHEMA publications. - */ - if (!pub->alltables && - !SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP, - ObjectIdGetDatum(schemaid), - ObjectIdGetDatum(pub->oid))) - pubtuple = SearchSysCacheCopy2(PUBLICATIONRELMAP, - ObjectIdGetDatum(relid), - ObjectIdGetDatum(pub->oid)); - - if (HeapTupleIsValid(pubtuple)) - { - /* Lookup the column list attribute. */ - values[2] = SysCacheGetAttr(PUBLICATIONRELMAP, pubtuple, - Anum_pg_publication_rel_prattrs, - &(nulls[2])); - - /* Null indicates no filter. */ - values[3] = SysCacheGetAttr(PUBLICATIONRELMAP, pubtuple, - Anum_pg_publication_rel_prqual, - &(nulls[3])); - } - else - { - nulls[2] = true; - nulls[3] = true; - } + SRF_RETURN_DONE(funcctx); +} - /* Show all columns when the column list is not specified. */ - if (nulls[2]) - { - Relation rel = table_open(relid, AccessShareLock); - int nattnums = 0; - int16 *attnums; - TupleDesc desc = RelationGetDescr(rel); - int i; +/* + * Similar to pg_get_publication_tables(), but retrieves publication + * information only for the specified table. + */ +Datum +pg_get_publication_table_info(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + published_rel *table_info = NULL; - attnums = palloc_array(int16, desc->natts); + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + Oid relid; + Name pubname; + Relation rel; + Publication *pub; + published_rel *pubrel = NULL; - for (i = 0; i < desc->natts; i++) - { - Form_pg_attribute att = TupleDescAttr(desc, i); + /* create a function context for cross-call persistence */ + funcctx = SRF_FIRSTCALL_INIT(); - if (att->attisdropped) - continue; + /* switch to memory context appropriate for multiple function calls */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); - if (att->attgenerated) - { - /* We only support replication of STORED generated cols. */ - if (att->attgenerated != ATTRIBUTE_GENERATED_STORED) - continue; - - /* - * User hasn't requested to replicate STORED generated - * cols. - */ - if (pub->pubgencols_type != PUBLISH_GENCOLS_STORED) - continue; - } - - attnums[nattnums++] = att->attnum; - } + relid = PG_GETARG_OID(0); + pubname = PG_GETARG_NAME(1); - if (nattnums > 0) - { - values[2] = PointerGetDatum(buildint2vector(attnums, nattnums)); - nulls[2] = false; - } + rel = table_open(relid, AccessShareLock); + pub = GetPublicationByName(NameStr(*pubname), false); - table_close(rel, AccessShareLock); + if (is_table_publishable_in_publication(relid, pub)) + { + pubrel = palloc_object(published_rel); + pubrel->relid = relid; + pubrel->pubid = pub->oid; } - rettuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + table_close(rel, AccessShareLock); + + /* Construct a tuple descriptor for the result rows. */ + funcctx->tuple_desc = create_published_rel_tuple_desc(); + funcctx->user_fctx = pubrel; + + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + table_info = (published_rel *) funcctx->user_fctx; + + /* The function returns zero or one tuple */ + if (table_info && funcctx->call_cntr == 0) + { + HeapTuple rettuple; + + rettuple = construct_published_rel_tuple(table_info, funcctx->tuple_desc); SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(rettuple)); } diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c index f49a4852ecb..ce7afd68533 100644 --- a/src/backend/replication/logical/tablesync.c +++ b/src/backend/replication/logical/tablesync.c @@ -798,17 +798,34 @@ 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) + { + /* pg_get_publication_table_info() is available since vesion 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_table_info(%u, p.pubname) 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 +999,27 @@ 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) + { + /* pg_get_publication_table_info() is available since version 19 */ + appendStringInfo(&cmd, + "SELECT DISTINCT pg_get_expr(gpt.qual, gpt.relid)" + " FROM pg_publication p," + " LATERAL pg_get_publication_table_info(%u, p.pubname) 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 361e2cfffeb..b357a67ba7d 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -12451,6 +12451,15 @@ proargmodes => '{v,o,o,o,o}', proargnames => '{pubname,pubid,relid,attrs,qual}', prosrc => 'pg_get_publication_tables' }, +{ oid => '8060', + descr => 'get information of the table that is part of the specified publication', + proname => 'pg_get_publication_table_info', prorows => '1', + proretset => 't', provolatile => 's', + prorettype => 'record', proargtypes => 'oid name', + proallargtypes => '{oid,name,oid,oid,int2vector,pg_node_tree}', + proargmodes => '{i,i,o,o,o,o}', + proargnames => '{relid,pubname,pubid,relid,attrs,qual}', + prosrc => 'pg_get_publication_table_info' }, { 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..e9914c147fa 100644 --- a/src/test/regress/expected/publication.out +++ b/src/test/regress/expected/publication.out @@ -2182,6 +2182,135 @@ 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_table_info() 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 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); +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_table_info(relname::regclass::oid, pubname) 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..75f1bc2f2fc 100644 --- a/src/test/regress/sql/publication.sql +++ b/src/test/regress/sql/publication.sql @@ -1378,6 +1378,73 @@ RESET SESSION AUTHORIZATION; DROP ROLE regress_publication_user, regress_publication_user2; DROP ROLE regress_publication_user_dummy; +-- Test pg_get_publication_table_info() 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 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); + +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_table_info(relname::regclass::oid, pubname) 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