From b041341919f026a229f59c198f191e7c4becbcbf Mon Sep 17 00:00:00 2001
From: Fujii Masao <fujii@postgresql.org>
Date: Thu, 6 Aug 2026 11:29:47 +0900
Subject: [PATCH v4] Distinguish publication exclusions in object addresses.

A pg_publication_rel entry can represent either an explicitly published
relation or a table excluded from a FOR ALL TABLES publication, but the
object address code treated every entry as a published relation without
checking prexcept. EXCEPT entries thus appeared as ordinary
published-table mappings in dependency messages, pg_identify_object(),
and pg_identify_object_as_address().

Report such entries as "publication excluded relation" instead, with
object identities indicating that the table is excluded from the
publication. pg_get_object_address() also accepts this new object type,
so that the output of pg_identify_object_as_address() can still be
passed back to it to look up the same object. "publication relation"
now resolves only non-EXCEPT entries, and "publication excluded
relation" only EXCEPT entries.

Oversight in commit fd366065e06.

Author: Fujii Masao <masao.fujii@gmail.com>
Author: Vignesh C <vignesh21@gmail.com>
Reviewed-by: shveta malik <shveta.malik@gmail.com>
Reviewed-by: Peter Smith <smithpb2250@gmail.com>
Reviewed-by: Zhijie Hou <houzj.fnst@fujitsu.com>
Reviewed-by: Nisha Moond <nisha.moond412@gmail.com>
Reviewed-by: Amit Kapila <amit.kapila16@gmail.com>
Discussion: https://postgr.es/m/CAHGQGwHfESBexa7fq99EvFCf31av=O9h9udnw22ymxmm6LMZzw@mail.gmail.com
Backpatch-through: 19, where it was introduced
---
 src/backend/catalog/aclchk.c                 |   2 +
 src/backend/catalog/objectaddress.c          | 125 ++++++++++++++++---
 src/backend/commands/dropcmds.c              |   1 +
 src/backend/commands/event_trigger.c         |   2 +
 src/backend/commands/seclabel.c              |   1 +
 src/include/nodes/parsenodes.h               |   1 +
 src/test/regress/expected/object_address.out |  26 +++-
 src/test/regress/expected/publication.out    |  35 ++++++
 src/test/regress/sql/object_address.sql      |  20 ++-
 src/test/regress/sql/publication.sql         |  29 +++++
 10 files changed, 223 insertions(+), 19 deletions(-)

diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index e67358f3858..2bbcc3e824b 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -2765,6 +2765,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_DEFAULT:
 					case OBJECT_DEFACL:
 					case OBJECT_DOMCONSTRAINT:
+					case OBJECT_PUBLICATION_EXCLUDED_REL:
 					case OBJECT_PUBLICATION_NAMESPACE:
 					case OBJECT_PUBLICATION_REL:
 					case OBJECT_ROLE:
@@ -2906,6 +2907,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_DEFACL:
 					case OBJECT_DOMCONSTRAINT:
 					case OBJECT_PARAMETER_ACL:
+					case OBJECT_PUBLICATION_EXCLUDED_REL:
 					case OBJECT_PUBLICATION_NAMESPACE:
 					case OBJECT_PUBLICATION_REL:
 					case OBJECT_ROLE:
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index b08e076e65f..8b119c82528 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -823,6 +823,9 @@ static const struct object_type_map
 	{
 		"publication relation", OBJECT_PUBLICATION_REL
 	},
+	{
+		"publication excluded relation", OBJECT_PUBLICATION_EXCLUDED_REL
+	},
 	{
 		"subscription", OBJECT_SUBSCRIPTION
 	},
@@ -863,7 +866,8 @@ static ObjectAddress get_object_address_opf_member(ObjectType objtype,
 
 static ObjectAddress get_object_address_usermapping(List *object,
 													bool missing_ok);
-static ObjectAddress get_object_address_publication_rel(List *object,
+static ObjectAddress get_object_address_publication_rel(ObjectType objtype,
+														List *object,
 														Relation *relp,
 														bool missing_ok);
 static ObjectAddress get_object_address_publication_schema(List *object,
@@ -880,6 +884,9 @@ static void getRelationTypeDescription(StringInfo buffer, Oid relid,
 									   int32 objectSubId, bool missing_ok);
 static void getProcedureTypeDescription(StringInfo buffer, Oid procid,
 										bool missing_ok);
+static void getPublicationRelationTypeDescription(StringInfo buffer,
+												  Oid pubreloid,
+												  bool missing_ok);
 static void getConstraintTypeDescription(StringInfo buffer, Oid constroid,
 										 bool missing_ok);
 static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
@@ -1112,8 +1119,10 @@ get_object_address(ObjectType objtype, Node *object,
 				address = get_object_address_publication_schema(castNode(List, object),
 																missing_ok);
 				break;
+			case OBJECT_PUBLICATION_EXCLUDED_REL:
 			case OBJECT_PUBLICATION_REL:
-				address = get_object_address_publication_rel(castNode(List, object),
+				address = get_object_address_publication_rel(objtype,
+															 castNode(List, object),
 															 &relation,
 															 missing_ok);
 				break;
@@ -1860,12 +1869,12 @@ get_object_address_usermapping(List *object, bool missing_ok)
 }
 
 /*
- * Find the ObjectAddress for a publication relation.  The first element of
- * the object parameter is the relation name, the second is the
- * publication name.
+ * Find the ObjectAddress for a published or excluded publication relation.
+ * The first element of the object parameter is the relation name, the second
+ * is the publication name.
  */
 static ObjectAddress
-get_object_address_publication_rel(List *object,
+get_object_address_publication_rel(ObjectType objtype, List *object,
 								   Relation *relp, bool missing_ok)
 {
 	ObjectAddress address;
@@ -1873,6 +1882,9 @@ get_object_address_publication_rel(List *object,
 	List	   *relname;
 	char	   *pubname;
 	Publication *pub;
+	Form_pg_publication_rel prform;
+	HeapTuple	tup;
+	bool		isexcept;
 
 	ObjectAddressSet(address, PublicationRelRelationId, InvalidOid);
 
@@ -1893,12 +1905,15 @@ get_object_address_publication_rel(List *object,
 		return address;
 	}
 
-	/* Find the publication relation mapping in syscache. */
-	address.objectId =
-		GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
-						ObjectIdGetDatum(RelationGetRelid(relation)),
-						ObjectIdGetDatum(pub->oid));
-	if (!OidIsValid(address.objectId))
+	/*
+	 * Find the publication relation mapping in syscache.  Fetch the tuple
+	 * rather than just its OID, so that prexcept can be checked without a
+	 * second lookup.
+	 */
+	tup = SearchSysCache2(PUBLICATIONRELMAP,
+						  ObjectIdGetDatum(RelationGetRelid(relation)),
+						  ObjectIdGetDatum(pub->oid));
+	if (!HeapTupleIsValid(tup))
 	{
 		if (!missing_ok)
 			ereport(ERROR,
@@ -1909,6 +1924,30 @@ get_object_address_publication_rel(List *object,
 		return address;
 	}
 
+	prform = (Form_pg_publication_rel) GETSTRUCT(tup);
+	address.objectId = prform->oid;
+	isexcept = prform->prexcept;
+	ReleaseSysCache(tup);
+
+	Assert(objtype == OBJECT_PUBLICATION_REL ||
+		   objtype == OBJECT_PUBLICATION_EXCLUDED_REL);
+
+	/*
+	 * The same relation and publication pair identifies either a published or
+	 * an excluded relation, so reject an entry of the kind that was not asked
+	 * for.
+	 */
+	if (objtype == OBJECT_PUBLICATION_EXCLUDED_REL && !isexcept)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is not an excluded relation of publication \"%s\"",
+						RelationGetRelationName(relation), pubname)));
+	else if (objtype == OBJECT_PUBLICATION_REL && isexcept)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is not a published relation of publication \"%s\"",
+						RelationGetRelationName(relation), pubname)));
+
 	*relp = relation;
 	return address;
 }
@@ -2235,6 +2274,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
 			pg_fallthrough;
 		case OBJECT_DOMCONSTRAINT:
 		case OBJECT_CAST:
+		case OBJECT_PUBLICATION_EXCLUDED_REL:
 		case OBJECT_PUBLICATION_REL:
 		case OBJECT_DEFACL:
 		case OBJECT_TRANSFORM:
@@ -2326,6 +2366,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
 		case OBJECT_TRANSFORM:
 			objnode = (Node *) list_make2(typename, linitial(args));
 			break;
+		case OBJECT_PUBLICATION_EXCLUDED_REL:
 		case OBJECT_PUBLICATION_REL:
 			objnode = (Node *) list_make2(name, linitial(args));
 			break;
@@ -2554,6 +2595,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 		case OBJECT_AMPROC:
 		case OBJECT_DEFAULT:
 		case OBJECT_DEFACL:
+		case OBJECT_PUBLICATION_EXCLUDED_REL:
 		case OBJECT_PUBLICATION_NAMESPACE:
 		case OBJECT_PUBLICATION_REL:
 		case OBJECT_USER_MAPPING:
@@ -4020,9 +4062,18 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 				initStringInfo(&rel);
 				getRelationDescription(&rel, prform->prrelid, false);
 
-				/* translator: first %s is, e.g., "table %s" */
-				appendStringInfo(&buffer, _("publication of %s in publication %s"),
-								 rel.data, pubname);
+				if (prform->prexcept)
+				{
+					/* translator: first %s is, e.g., "table %s" */
+					appendStringInfo(&buffer, _("exclusion of %s from publication %s"),
+									 rel.data, pubname);
+				}
+				else
+				{
+					/* translator: first %s is, e.g., "table %s" */
+					appendStringInfo(&buffer, _("publication of %s in publication %s"),
+									 rel.data, pubname);
+				}
 				pfree(rel.data);
 				ReleaseSysCache(tup);
 				break;
@@ -4654,7 +4705,8 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 			break;
 
 		case PublicationRelRelationId:
-			appendStringInfoString(&buffer, "publication relation");
+			getPublicationRelationTypeDescription(&buffer, object->objectId,
+												  missing_ok);
 			break;
 
 		case SubscriptionRelationId:
@@ -4808,6 +4860,41 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid,
 	ReleaseSysCache(procTup);
 }
 
+/*
+ * subroutine for getObjectTypeDescription: describe a publication relation
+ *
+ * Appends "publication excluded relation" for EXCEPT entries, or
+ * "publication relation" for published relations, to buffer.
+ */
+static void
+getPublicationRelationTypeDescription(StringInfo buffer, Oid pubreloid,
+									  bool missing_ok)
+{
+	HeapTuple	tup;
+	Form_pg_publication_rel prform;
+
+	tup = SearchSysCache1(PUBLICATIONREL, ObjectIdGetDatum(pubreloid));
+	if (!HeapTupleIsValid(tup))
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for publication table %u",
+				 pubreloid);
+
+		/* fallback to "publication relation" for an undefined object */
+		appendStringInfoString(buffer, "publication relation");
+		return;
+	}
+
+	prform = (Form_pg_publication_rel) GETSTRUCT(tup);
+
+	if (prform->prexcept)
+		appendStringInfoString(buffer, "publication excluded relation");
+	else
+		appendStringInfoString(buffer, "publication relation");
+
+	ReleaseSysCache(tup);
+}
+
 /*
  * Obtain a given object's identity, as a palloc'ed string.
  *
@@ -5949,7 +6036,11 @@ getObjectIdentityParts(const ObjectAddress *object,
 				pubname = get_publication_name(prform->prpubid, false);
 
 				getRelationIdentity(&buffer, prform->prrelid, objname, false);
-				appendStringInfo(&buffer, " in publication %s", pubname);
+				if (prform->prexcept)
+					appendStringInfo(&buffer, " excluded from publication %s",
+									 pubname);
+				else
+					appendStringInfo(&buffer, " in publication %s", pubname);
 
 				if (objargs)
 					*objargs = list_make1(pubname);
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index 92526012d2a..228959e1050 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -504,6 +504,7 @@ does_not_exist_skipping(ObjectType objtype, Node *object)
 		case OBJECT_DOMCONSTRAINT:
 		case OBJECT_LARGEOBJECT:
 		case OBJECT_PARAMETER_ACL:
+		case OBJECT_PUBLICATION_EXCLUDED_REL:
 		case OBJECT_PUBLICATION_NAMESPACE:
 		case OBJECT_PUBLICATION_REL:
 		case OBJECT_TABCONSTRAINT:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index d868c7f42c3..08f8f4c3f24 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -2316,6 +2316,7 @@ stringify_grant_objtype(ObjectType objtype)
 		case OBJECT_OPFAMILY:
 		case OBJECT_POLICY:
 		case OBJECT_PUBLICATION:
+		case OBJECT_PUBLICATION_EXCLUDED_REL:
 		case OBJECT_PUBLICATION_NAMESPACE:
 		case OBJECT_PUBLICATION_REL:
 		case OBJECT_ROLE:
@@ -2400,6 +2401,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
 		case OBJECT_PARAMETER_ACL:
 		case OBJECT_POLICY:
 		case OBJECT_PUBLICATION:
+		case OBJECT_PUBLICATION_EXCLUDED_REL:
 		case OBJECT_PUBLICATION_NAMESPACE:
 		case OBJECT_PUBLICATION_REL:
 		case OBJECT_ROLE:
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5b80396723c..77e3640e626 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
 		case OBJECT_OPFAMILY:
 		case OBJECT_PARAMETER_ACL:
 		case OBJECT_POLICY:
+		case OBJECT_PUBLICATION_EXCLUDED_REL:
 		case OBJECT_PUBLICATION_NAMESPACE:
 		case OBJECT_PUBLICATION_REL:
 		case OBJECT_RULE:
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index aea7311faad..0debcd193ab 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2395,6 +2395,7 @@ typedef enum ObjectType
 	OBJECT_POLICY,
 	OBJECT_PROCEDURE,
 	OBJECT_PUBLICATION,
+	OBJECT_PUBLICATION_EXCLUDED_REL,
 	OBJECT_PUBLICATION_NAMESPACE,
 	OBJECT_PUBLICATION_REL,
 	OBJECT_ROLE,
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 4c266d7fa67..101bf3cdfac 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -111,7 +111,8 @@ BEGIN
         ('text search template'), ('text search configuration'),
         ('policy'), ('user mapping'), ('default acl'), ('transform'),
         ('operator of access method'), ('function of access method'),
-        ('publication namespace'), ('publication relation')
+        ('publication namespace'), ('publication relation'),
+        ('publication excluded relation')
     LOOP
         FOR names IN VALUES ('{eins}'), ('{addr_nsp, zwei}'), ('{eins, zwei, drei}')
         LOOP
@@ -331,6 +332,12 @@ WARNING:  error for publication relation,{addr_nsp,zwei},{}: argument list lengt
 WARNING:  error for publication relation,{addr_nsp,zwei},{integer}: relation "addr_nsp.zwei" does not exist
 WARNING:  error for publication relation,{eins,zwei,drei},{}: argument list length must be exactly 1
 WARNING:  error for publication relation,{eins,zwei,drei},{integer}: cross-database references are not implemented: "eins.zwei.drei"
+WARNING:  error for publication excluded relation,{eins},{}: argument list length must be exactly 1
+WARNING:  error for publication excluded relation,{eins},{integer}: relation "eins" does not exist
+WARNING:  error for publication excluded relation,{addr_nsp,zwei},{}: argument list length must be exactly 1
+WARNING:  error for publication excluded relation,{addr_nsp,zwei},{integer}: relation "addr_nsp.zwei" does not exist
+WARNING:  error for publication excluded relation,{eins,zwei,drei},{}: argument list length must be exactly 1
+WARNING:  error for publication excluded relation,{eins,zwei,drei},{integer}: cross-database references are not implemented: "eins.zwei.drei"
 -- these object types cannot be qualified names
 SELECT pg_get_object_address('language', '{one}', '{}');
 ERROR:  language "one" does not exist
@@ -509,6 +516,23 @@ subscription|NULL|regress_addr_sub|regress_addr_sub|t
 publication|NULL|addr_pub|addr_pub|t
 publication relation|NULL|NULL|addr_nsp.gentable in publication addr_pub|t
 publication namespace|NULL|NULL|addr_nsp in publication addr_pub_schema|t
+-- A FOR ALL TABLES publication is listed by \d for every table in the
+-- database, which would disturb the other tests running concurrently in this
+-- parallel group.  Create it in a transaction that is rolled back, so that it
+-- is never visible to another session.
+BEGIN;
+SET LOCAL client_min_messages = 'ERROR';
+CREATE PUBLICATION addr_pub_except FOR ALL TABLES EXCEPT (TABLE addr_nsp.gentable);
+SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
+       ROW(pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)) =
+         ROW(pg_identify_object(addr2.classid, addr2.objid, addr2.objsubid)) AS roundtrip
+FROM pg_get_object_address('publication excluded relation',
+                           '{addr_nsp, gentable}',
+                           '{addr_pub_except}') AS addr1,
+     pg_identify_object_as_address(classid, objid, objsubid) AS ioa (typ, nms, args),
+     pg_get_object_address(typ, nms, ioa.args) AS addr2;
+publication excluded relation|NULL|NULL|addr_nsp.gentable excluded from publication addr_pub_except|t
+ROLLBACK;
 ---
 --- Cleanup resources
 ---
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index fe602c4538e..7ccfb5810a6 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -274,6 +274,41 @@ Excluded from publications:
     "testpub_foralltables_excepttable"
     "testpub_foralltables_excepttable1"
 
+-- Check object address handling for an EXCEPT entry.
+\a\t
+SELECT (pg_identify_object('pg_publication_rel'::regclass, pr.oid, 0)).*
+FROM pg_publication_rel pr
+JOIN pg_publication p ON p.oid = pr.prpubid
+JOIN pg_class c ON c.oid = pr.prrelid
+WHERE p.pubname = 'testpub_foralltables_excepttable1'
+  AND c.relname = 'testpub_tbl1';
+publication excluded relation|||public.testpub_tbl1 excluded from publication testpub_foralltables_excepttable1
+-- testpub_describe publishes testpub_tbl1, testpub_foralltables_excepttable1
+-- excludes it; an entry of one kind must not be resolved as the other.
+CREATE PUBLICATION testpub_describe FOR TABLE testpub_tbl1;
+SELECT pg_get_object_address('publication excluded relation',
+                             '{public, testpub_tbl1}', '{testpub_describe}');
+ERROR:  "testpub_tbl1" is not an excluded relation of publication "testpub_describe"
+SELECT pg_get_object_address('publication relation',
+                             '{public, testpub_tbl1}',
+                             '{testpub_foralltables_excepttable1}');
+ERROR:  "testpub_tbl1" is not a published relation of publication "testpub_foralltables_excepttable1"
+-- No entry of either kind.  testpub_default publishes nothing.
+SELECT pg_get_object_address('publication excluded relation',
+                             '{public, testpub_tbl1}', '{testpub_default}');
+ERROR:  publication relation "testpub_tbl1" in publication "testpub_default" does not exist
+-- Check pg_describe_object output for both included and excluded entries
+SELECT p.pubname,
+       pg_describe_object('pg_publication_rel'::regclass, pr.oid, 0) AS description,
+       pr.prexcept
+FROM pg_publication_rel pr
+JOIN pg_publication p ON p.oid = pr.prpubid
+WHERE p.pubname IN ('testpub_describe', 'testpub_foralltables_excepttable1')
+ORDER BY p.pubname;
+testpub_describe|publication of table testpub_tbl1 in publication testpub_describe|f
+testpub_foralltables_excepttable1|exclusion of table testpub_tbl1 from publication testpub_foralltables_excepttable1|t
+DROP PUBLICATION testpub_describe;
+\a\t
 -- fail - first table in the EXCEPT list should use TABLE keyword
 CREATE PUBLICATION testpub_foralltables_excepttable2 FOR ALL TABLES EXCEPT (testpub_tbl1, testpub_tbl2);
 ERROR:  syntax error at or near "testpub_tbl1"
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 653a53038e3..3e4638b905b 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -103,7 +103,8 @@ BEGIN
         ('text search template'), ('text search configuration'),
         ('policy'), ('user mapping'), ('default acl'), ('transform'),
         ('operator of access method'), ('function of access method'),
-        ('publication namespace'), ('publication relation')
+        ('publication namespace'), ('publication relation'),
+        ('publication excluded relation')
     LOOP
         FOR names IN VALUES ('{eins}'), ('{addr_nsp, zwei}'), ('{eins, zwei, drei}')
         LOOP
@@ -225,6 +226,23 @@ FROM objects,
      pg_get_object_address(typ, nms, ioa.args) AS addr2
 ORDER BY addr1.classid, addr1.objid, addr1.objsubid;
 
+-- A FOR ALL TABLES publication is listed by \d for every table in the
+-- database, which would disturb the other tests running concurrently in this
+-- parallel group.  Create it in a transaction that is rolled back, so that it
+-- is never visible to another session.
+BEGIN;
+SET LOCAL client_min_messages = 'ERROR';
+CREATE PUBLICATION addr_pub_except FOR ALL TABLES EXCEPT (TABLE addr_nsp.gentable);
+SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
+       ROW(pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)) =
+         ROW(pg_identify_object(addr2.classid, addr2.objid, addr2.objsubid)) AS roundtrip
+FROM pg_get_object_address('publication excluded relation',
+                           '{addr_nsp, gentable}',
+                           '{addr_pub_except}') AS addr1,
+     pg_identify_object_as_address(classid, objid, objsubid) AS ioa (typ, nms, args),
+     pg_get_object_address(typ, nms, ioa.args) AS addr2;
+ROLLBACK;
+
 ---
 --- Cleanup resources
 ---
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 80c244c9138..074482605d9 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -123,6 +123,35 @@ CREATE PUBLICATION testpub_foralltables_excepttable1 FOR ALL TABLES EXCEPT (TABL
 -- Check that the table description shows the publications where it is listed
 -- in the EXCEPT clause
 \d testpub_tbl1
+-- Check object address handling for an EXCEPT entry.
+\a\t
+SELECT (pg_identify_object('pg_publication_rel'::regclass, pr.oid, 0)).*
+FROM pg_publication_rel pr
+JOIN pg_publication p ON p.oid = pr.prpubid
+JOIN pg_class c ON c.oid = pr.prrelid
+WHERE p.pubname = 'testpub_foralltables_excepttable1'
+  AND c.relname = 'testpub_tbl1';
+-- testpub_describe publishes testpub_tbl1, testpub_foralltables_excepttable1
+-- excludes it; an entry of one kind must not be resolved as the other.
+CREATE PUBLICATION testpub_describe FOR TABLE testpub_tbl1;
+SELECT pg_get_object_address('publication excluded relation',
+                             '{public, testpub_tbl1}', '{testpub_describe}');
+SELECT pg_get_object_address('publication relation',
+                             '{public, testpub_tbl1}',
+                             '{testpub_foralltables_excepttable1}');
+-- No entry of either kind.  testpub_default publishes nothing.
+SELECT pg_get_object_address('publication excluded relation',
+                             '{public, testpub_tbl1}', '{testpub_default}');
+-- Check pg_describe_object output for both included and excluded entries
+SELECT p.pubname,
+       pg_describe_object('pg_publication_rel'::regclass, pr.oid, 0) AS description,
+       pr.prexcept
+FROM pg_publication_rel pr
+JOIN pg_publication p ON p.oid = pr.prpubid
+WHERE p.pubname IN ('testpub_describe', 'testpub_foralltables_excepttable1')
+ORDER BY p.pubname;
+DROP PUBLICATION testpub_describe;
+\a\t
 -- fail - first table in the EXCEPT list should use TABLE keyword
 CREATE PUBLICATION testpub_foralltables_excepttable2 FOR ALL TABLES EXCEPT (testpub_tbl1, testpub_tbl2);
 
-- 
2.54.0

