1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00

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 fd366065e0.

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>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Reviewed-by: Manuel Reyes Bravo <manuelreyesbravo@gmail.com>
Discussion: https://postgr.es/m/CAHGQGwHfESBexa7fq99EvFCf31av=O9h9udnw22ymxmm6LMZzw@mail.gmail.com
Backpatch-through: 19, where it was introduced
This commit is contained in:
Amit Kapila
2026-09-17 09:04:31 +05:30
parent 3201ea8f17
commit 91ff666f1d
10 changed files with 235 additions and 23 deletions

View File

@@ -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:

View File

@@ -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,12 @@ get_object_address_publication_rel(List *object,
List *relname;
char *pubname;
Publication *pub;
Form_pg_publication_rel prform;
HeapTuple tup;
bool isexcept;
Assert(objtype == OBJECT_PUBLICATION_REL ||
objtype == OBJECT_PUBLICATION_EXCLUDED_REL);
ObjectAddressSet(address, PublicationRelRelationId, InvalidOid);
@@ -1893,22 +1908,54 @@ 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,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("publication relation \"%s\" in publication \"%s\" does not exist",
RelationGetRelationName(relation), pubname)));
{
if (objtype == OBJECT_PUBLICATION_EXCLUDED_REL)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("publication excluded relation \"%s\" in publication \"%s\" does not exist",
RelationGetRelationName(relation), pubname)));
else
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("publication relation \"%s\" in publication \"%s\" does not exist",
RelationGetRelationName(relation), pubname)));
}
relation_close(relation, AccessShareLock);
return address;
}
prform = (Form_pg_publication_rel) GETSTRUCT(tup);
address.objectId = prform->oid;
isexcept = prform->prexcept;
ReleaseSysCache(tup);
/*
* 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 +2282,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 +2374,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 +2603,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:
@@ -4025,9 +4075,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;
@@ -4659,7 +4718,8 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
break;
case PublicationRelRelationId:
appendStringInfoString(&buffer, "publication relation");
getPublicationRelationTypeDescription(&buffer, object->objectId,
missing_ok);
break;
case SubscriptionRelationId:
@@ -4813,6 +4873,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.
*
@@ -5954,7 +6049,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);

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -2351,6 +2351,7 @@ typedef enum ObjectType
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_EXCLUDED_REL,
OBJECT_PUBLICATION_NAMESPACE,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,

View File

@@ -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
---

View File

@@ -265,6 +265,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 excluded 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"

View File

@@ -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
---

View File

@@ -121,6 +121,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);