From bec0384109927a3d65320cf395cbb970b6d9f05c Mon Sep 17 00:00:00 2001 From: Dilip Kumar Date: Tue, 16 Dec 2025 14:02:55 +0530 Subject: [PATCH v1] Add relispublishable column to pg_class catalog Previously, determining if a relation was eligible for logical replication was performed dynamically via is_publishable_class(). This function relied on hard-coded OID checks and and relkind that is performed at runtime. This patch change by adding a "relispublishable" boolean column to pg_class. With this we don't need to check OID and relkind dynamically we can just rely on ispublishable field. Author: Dilip Kumar based on suggestion by Amit Kapila --- src/backend/bootstrap/bootparse.y | 1 + src/backend/catalog/heap.c | 4 ++ src/backend/catalog/pg_publication.c | 49 ++------------------- src/backend/catalog/toasting.c | 1 + src/backend/commands/cluster.c | 1 + src/backend/commands/tablecmds.c | 15 +++++++ src/backend/replication/pgoutput/pgoutput.c | 4 +- src/backend/utils/cache/relcache.c | 2 +- src/include/catalog/heap.h | 1 + src/include/catalog/pg_class.h | 3 ++ src/include/catalog/pg_publication.h | 1 - 11 files changed, 33 insertions(+), 49 deletions(-) diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y index 9833f52c1be..01e4bd301e6 100644 --- a/src/backend/bootstrap/bootparse.y +++ b/src/backend/bootstrap/bootparse.y @@ -243,6 +243,7 @@ Boot_CreateStmt: false, true, false, + false, InvalidOid, NULL); elog(DEBUG4, "relation created with OID %u", id); diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 265cc3e5fbf..5760794853c 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -953,6 +953,7 @@ InsertPgClassTuple(Relation pg_class_desc, values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite); values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid); values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid); + values[Anum_pg_class_relispublishable - 1] = BoolGetDatum(rd_rel->relispublishable); if (relacl != (Datum) 0) values[Anum_pg_class_relacl - 1] = relacl; else @@ -1138,6 +1139,7 @@ heap_create_with_catalog(const char *relname, bool use_user_acl, bool allow_system_table_mods, bool is_internal, + bool ispublisable, Oid relrewrite, ObjectAddress *typaddress) { @@ -1329,6 +1331,8 @@ heap_create_with_catalog(const char *relname, Assert(relid == RelationGetRelid(new_rel_desc)); new_rel_desc->rd_rel->relrewrite = relrewrite; + new_rel_desc->rd_rel->relispublishable = (ispublisable && + relid >= FirstNormalObjectId); /* * Decide whether to create a pg_type entry for the relation's rowtype. diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c index 7aa3f179924..13aa7b273b3 100644 --- a/src/backend/catalog/pg_publication.c +++ b/src/backend/catalog/pg_publication.c @@ -111,47 +111,6 @@ check_publication_add_schema(Oid schemaid) errdetail("Temporary schemas cannot be replicated."))); } -/* - * Returns if relation represented by oid and Form_pg_class entry - * is publishable. - * - * Does same checks as check_publication_add_relation() above except for - * RELKIND_SEQUENCE, but does not need relation to be opened and also does - * not throw errors. Here, the additional check is to support ALL SEQUENCES - * publication. - * - * XXX This also excludes all tables with relid < FirstNormalObjectId, - * ie all tables created during initdb. This mainly affects the preinstalled - * information_schema. IsCatalogRelationOid() only excludes tables with - * relid < FirstUnpinnedObjectId, making that test rather redundant, - * but really we should get rid of the FirstNormalObjectId test not - * IsCatalogRelationOid. We can't do so today because we don't want - * information_schema tables to be considered publishable; but this test - * is really inadequate for that, since the information_schema could be - * dropped and reloaded and then it'll be considered publishable. The best - * long-term solution may be to add a "relispublishable" bool to pg_class, - * and depend on that instead of OID checks. - */ -static bool -is_publishable_class(Oid relid, Form_pg_class reltuple) -{ - return (reltuple->relkind == RELKIND_RELATION || - reltuple->relkind == RELKIND_PARTITIONED_TABLE || - reltuple->relkind == RELKIND_SEQUENCE) && - !IsCatalogRelationOid(relid) && - reltuple->relpersistence == RELPERSISTENCE_PERMANENT && - relid >= FirstNormalObjectId; -} - -/* - * Another variant of is_publishable_class(), taking a Relation. - */ -bool -is_publishable_relation(Relation rel) -{ - return is_publishable_class(RelationGetRelid(rel), rel->rd_rel); -} - /* * SQL-callable variant of the above * @@ -169,7 +128,7 @@ pg_relation_is_publishable(PG_FUNCTION_ARGS) tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); if (!HeapTupleIsValid(tuple)) PG_RETURN_NULL(); - result = is_publishable_class(relid, (Form_pg_class) GETSTRUCT(tuple)); + result = ((Form_pg_class) GETSTRUCT(tuple))->relispublishable; ReleaseSysCache(tuple); PG_RETURN_BOOL(result); } @@ -890,7 +849,7 @@ GetAllPublicationRelations(char relkind, bool pubviaroot) Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple); Oid relid = relForm->oid; - if (is_publishable_class(relid, relForm) && + if (relForm->relispublishable && !(relForm->relispartition && pubviaroot)) result = lappend_oid(result, relid); } @@ -911,7 +870,7 @@ GetAllPublicationRelations(char relkind, bool pubviaroot) Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple); Oid relid = relForm->oid; - if (is_publishable_class(relid, relForm) && + if (relForm->relispublishable && !relForm->relispartition) result = lappend_oid(result, relid); } @@ -1018,7 +977,7 @@ GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt) Oid relid = relForm->oid; char relkind; - if (!is_publishable_class(relid, relForm)) + if (!relForm->relispublishable) continue; relkind = get_rel_relkind(relid); diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c index 874a8fc89ad..429ba2dcae7 100644 --- a/src/backend/catalog/toasting.c +++ b/src/backend/catalog/toasting.c @@ -263,6 +263,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, false, true, true, + false, OIDOldToast, NULL); Assert(toast_relid != InvalidOid); diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 2120c85ccb4..7528ae9e2e3 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -774,6 +774,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, false, true, true, + false, OIDOldHeap, NULL); Assert(OIDNewHeap != InvalidOid); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 953fadb9c6b..22d508c3ed7 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -790,6 +790,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ObjectAddress address; LOCKMODE parentLockmode; Oid accessMethodId = InvalidOid; + bool ispublishable; /* * Truncate relname to appropriate length (probably a waste of time, as @@ -1051,6 +1052,18 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, accessMethodId = get_table_am_oid(default_table_access_method, false); } + /* + * Determine if the relation is eligible for logical replication. + * + * To be publishable, the relation must be a regular table, a partitioned + * table, or a sequence. Additionally, it must be a permanent relation + * created during normal processing to exclude system catalogs. + */ + ispublishable = ((relkind == RELKIND_RELATION || + relkind == RELKIND_PARTITIONED_TABLE || + relkind == RELKIND_SEQUENCE) && + stmt->relation->relpersistence == RELPERSISTENCE_PERMANENT); + /* * Create the relation. Inherited defaults and CHECK constraints are * passed in for immediate handling --- since they don't need parsing, @@ -1076,6 +1089,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, true, allowSystemTableMods, false, + ispublishable, InvalidOid, typaddress); @@ -22529,6 +22543,7 @@ createPartitionTable(List **wqueue, RangeVar *newPartName, true, allowSystemTableMods, true, + parent_rel->rd_rel->relispublishable, InvalidOid, NULL); diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c index 787998abb8a..ead0b393d87 100644 --- a/src/backend/replication/pgoutput/pgoutput.c +++ b/src/backend/replication/pgoutput/pgoutput.c @@ -1491,7 +1491,7 @@ pgoutput_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, TupleTableSlot *old_slot = NULL; TupleTableSlot *new_slot = NULL; - if (!is_publishable_relation(relation)) + if (!relation->rd_rel->relispublishable) return; /* @@ -1675,7 +1675,7 @@ pgoutput_truncate(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, Relation relation = relations[i]; Oid relid = RelationGetRelid(relation); - if (!is_publishable_relation(relation)) + if (!relation->rd_rel->relispublishable) continue; relentry = get_rel_sync_entry(data, relation); diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 2d0cb7bcfd4..8d0596a9c8f 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -5804,7 +5804,7 @@ RelationBuildPublicationDesc(Relation relation, PublicationDesc *pubdesc) * If not publishable, it publishes no actions. (pgoutput_change() will * ignore it.) */ - if (!is_publishable_relation(relation)) + if (!relation->rd_rel->relispublishable) { memset(pubdesc, 0, sizeof(PublicationDesc)); pubdesc->rf_valid_for_update = true; diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index dbd339e9df4..3ecdb670e2c 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -83,6 +83,7 @@ extern Oid heap_create_with_catalog(const char *relname, bool use_user_acl, bool allow_system_table_mods, bool is_internal, + bool ispublishable, Oid relrewrite, ObjectAddress *typaddress); diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index 07d182da796..bf959094a85 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -122,6 +122,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat /* is relation a partition? */ bool relispartition BKI_DEFAULT(f); + /* is relation publishable */ + bool relispublishable BKI_DEFAULT(f); + /* link to original rel during table rewrite; otherwise 0 */ Oid relrewrite BKI_DEFAULT(0) BKI_LOOKUP_OPT(pg_class); diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h index 22f48bb8975..9c4613b62af 100644 --- a/src/include/catalog/pg_publication.h +++ b/src/include/catalog/pg_publication.h @@ -183,7 +183,6 @@ extern List *GetPubPartitionOptionRelations(List *result, extern Oid GetTopMostAncestorInPublication(Oid puboid, List *ancestors, int *ancestor_level); -extern bool is_publishable_relation(Relation rel); extern bool is_schema_publication(Oid pubid); extern bool check_and_fetch_column_list(Publication *pub, Oid relid, MemoryContext mcxt, Bitmapset **cols); -- 2.49.0