Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 13a185f

Browse files
author
Amit Kapila
committed
Allow publications with schema and table of the same schema.
We previously thought that allowing such cases can confuse users when they specify DROP TABLES IN SCHEMA but that doesn't seem to be the case based on discussion. This helps to uplift the restriction during ALTER TABLE ... SET SCHEMA which used to ensure that we couldn't end up with a publication having both a schema and the same schema's table. To allow this, we need to forbid having any schema on a publication if column lists on a table are specified (and vice versa). This is because otherwise we still need a restriction during ALTER TABLE ... SET SCHEMA to forbid cases where it could lead to a publication having both a schema and the same schema's table with column list. Based on suggestions by Peter Eisentraut. Author: Hou Zhijie and Vignesh C Reviewed-By: Peter Smith, Amit Kapila Backpatch-through: 15, where it was introduced Discussion: https://postgr.es/m/2729c9e2-9aac-8cda-f2f4-34f2bcc18f4e@enterprisedb.com
1 parent d89755d commit 13a185f

File tree

13 files changed

+251
-185
lines changed

13 files changed

+251
-185
lines changed

doc/src/sgml/logical-replication.sgml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,6 +1119,11 @@ test_sub=# SELECT * FROM child ORDER BY a;
11191119
of columns in the list is not preserved.
11201120
</para>
11211121

1122+
<para>
1123+
Specifying a column list when the publication also publishes
1124+
<literal>FOR TABLES IN SCHEMA</literal> is not supported.
1125+
</para>
1126+
11221127
<para>
11231128
For partitioned tables, the publication parameter
11241129
<literal>publish_via_partition_root</literal> determines which column list

doc/src/sgml/ref/alter_publication.sgml

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,11 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
5252
remove one or more tables/schemas from the publication. Note that adding
5353
tables/schemas to a publication that is already subscribed to will require an
5454
<literal>ALTER SUBSCRIPTION ... REFRESH PUBLICATION</literal> action on the
55-
subscribing side in order to become effective. Note also that the combination
56-
of <literal>DROP</literal> with a <literal>WHERE</literal> clause is not
57-
allowed.
55+
subscribing side in order to become effective. Note also that
56+
<literal>DROP TABLES IN SCHEMA</literal> will not drop any schema tables
57+
that were specified using <literal>FOR TABLE</literal>/
58+
<literal>ADD TABLE</literal>, and the combination of <literal>DROP</literal>
59+
with a <literal>WHERE</literal> clause is not allowed.
5860
</para>
5961

6062
<para>
@@ -82,11 +84,8 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
8284
</para>
8385

8486
<para>
85-
Adding/Setting a table that is part of schema specified in
86-
<literal>TABLES IN SCHEMA</literal>, adding/setting a schema to a
87-
publication that already has a table that is part of the specified schema or
88-
adding/setting a table to a publication that already has a table's schema as
89-
part of the specified schema is not supported.
87+
Adding/Setting any schema when the publication also publishes a table with a
88+
column list, and vice versa is not supported.
9089
</para>
9190
</refsect1>
9291

doc/src/sgml/ref/create_publication.sgml

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,18 +102,18 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
102102
materialized views, and regular views cannot be part of a publication.
103103
</para>
104104

105+
<para>
106+
Specifying a column list when the publication also publishes
107+
<literal>FOR TABLES IN SCHEMA</literal> is not supported.
108+
</para>
109+
105110
<para>
106111
When a partitioned table is added to a publication, all of its existing
107112
and future partitions are implicitly considered to be part of the
108113
publication. So, even operations that are performed directly on a
109114
partition are also published via publications that its ancestors are
110115
part of.
111116
</para>
112-
113-
<para>
114-
Specifying a table that is part of a schema specified by
115-
<literal>FOR TABLES IN SCHEMA</literal> is not supported.
116-
</para>
117117
</listitem>
118118
</varlistentry>
119119

@@ -136,8 +136,8 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
136136
</para>
137137

138138
<para>
139-
Specifying a schema along with a table which belongs to the specified
140-
schema using <literal>FOR TABLE</literal> is not supported.
139+
Specifying a schema when the publication also publishes a table with a
140+
column list is not supported.
141141
</para>
142142

143143
<para>
@@ -273,6 +273,12 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
273273
system columns.
274274
</para>
275275

276+
<para>
277+
The row filter on a table becomes redundant if
278+
<literal>FOR TABLES IN SCHEMA</literal> is specified and the table
279+
belongs to the referred schema.
280+
</para>
281+
276282
<para>
277283
For published partitioned tables, the row filter for each
278284
partition is taken from the published partitioned table if the

src/backend/catalog/pg_publication.c

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,6 +1111,7 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
11111111
HeapTuple pubtuple = NULL;
11121112
HeapTuple rettuple;
11131113
Oid relid = list_nth_oid(tables, funcctx->call_cntr);
1114+
Oid schemaid = get_rel_namespace(relid);
11141115
Datum values[NUM_PUBLICATION_TABLES_ELEM] = {0};
11151116
bool nulls[NUM_PUBLICATION_TABLES_ELEM] = {0};
11161117

@@ -1122,9 +1123,17 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
11221123

11231124
values[0] = ObjectIdGetDatum(relid);
11241125

1125-
pubtuple = SearchSysCacheCopy2(PUBLICATIONRELMAP,
1126-
ObjectIdGetDatum(relid),
1127-
ObjectIdGetDatum(publication->oid));
1126+
/*
1127+
* We don't consider row filters or column lists for FOR ALL TABLES or
1128+
* FOR TABLES IN SCHEMA publications.
1129+
*/
1130+
if (!publication->alltables &&
1131+
!SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
1132+
ObjectIdGetDatum(schemaid),
1133+
ObjectIdGetDatum(publication->oid)))
1134+
pubtuple = SearchSysCacheCopy2(PUBLICATIONRELMAP,
1135+
ObjectIdGetDatum(relid),
1136+
ObjectIdGetDatum(publication->oid));
11281137

11291138
if (HeapTupleIsValid(pubtuple))
11301139
{

src/backend/commands/publicationcmds.c

Lines changed: 55 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ typedef struct rf_context
6666
Oid parentid; /* relid of the parent relation */
6767
} rf_context;
6868

69-
static List *OpenRelIdList(List *relids);
7069
static List *OpenTableList(List *tables);
7170
static void CloseTableList(List *rels);
7271
static void LockSchemaList(List *schemalist);
@@ -214,44 +213,6 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
214213
}
215214
}
216215

217-
/*
218-
* Check if any of the given relation's schema is a member of the given schema
219-
* list.
220-
*/
221-
static void
222-
CheckObjSchemaNotAlreadyInPublication(List *rels, List *schemaidlist,
223-
PublicationObjSpecType checkobjtype)
224-
{
225-
ListCell *lc;
226-
227-
foreach(lc, rels)
228-
{
229-
PublicationRelInfo *pub_rel = (PublicationRelInfo *) lfirst(lc);
230-
Relation rel = pub_rel->relation;
231-
Oid relSchemaId = RelationGetNamespace(rel);
232-
233-
if (list_member_oid(schemaidlist, relSchemaId))
234-
{
235-
if (checkobjtype == PUBLICATIONOBJ_TABLES_IN_SCHEMA)
236-
ereport(ERROR,
237-
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
238-
errmsg("cannot add schema \"%s\" to publication",
239-
get_namespace_name(relSchemaId)),
240-
errdetail("Table \"%s\" in schema \"%s\" is already part of the publication, adding the same schema is not supported.",
241-
RelationGetRelationName(rel),
242-
get_namespace_name(relSchemaId)));
243-
else if (checkobjtype == PUBLICATIONOBJ_TABLE)
244-
ereport(ERROR,
245-
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
246-
errmsg("cannot add relation \"%s.%s\" to publication",
247-
get_namespace_name(relSchemaId),
248-
RelationGetRelationName(rel)),
249-
errdetail("Table's schema \"%s\" is already part of the publication or part of the specified schema list.",
250-
get_namespace_name(relSchemaId)));
251-
}
252-
}
253-
}
254-
255216
/*
256217
* Returns true if any of the columns used in the row filter WHERE expression is
257218
* not part of REPLICA IDENTITY, false otherwise.
@@ -721,7 +682,7 @@ TransformPubWhereClauses(List *tables, const char *queryString,
721682
*/
722683
static void
723684
CheckPubRelationColumnList(List *tables, const char *queryString,
724-
bool pubviaroot)
685+
bool publish_schema, bool pubviaroot)
725686
{
726687
ListCell *lc;
727688

@@ -732,6 +693,24 @@ CheckPubRelationColumnList(List *tables, const char *queryString,
732693
if (pri->columns == NIL)
733694
continue;
734695

696+
/*
697+
* Disallow specifying column list if any schema is in the
698+
* publication.
699+
*
700+
* XXX We could instead just forbid the case when the publication
701+
* tries to publish the table with a column list and a schema for that
702+
* table. However, if we do that then we need a restriction during
703+
* ALTER TABLE ... SET SCHEMA to prevent such a case which doesn't
704+
* seem to be a good idea.
705+
*/
706+
if (publish_schema)
707+
ereport(ERROR,
708+
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
709+
errmsg("cannot use publication column list for relation \"%s.%s\"",
710+
get_namespace_name(RelationGetNamespace(pri->relation)),
711+
RelationGetRelationName(pri->relation)),
712+
errdetail("Column list cannot be specified if any schema is part of the publication or specified in the list."));
713+
735714
/*
736715
* If the publication doesn't publish changes via the root partitioned
737716
* table, the partition's column list will be used. So disallow using
@@ -858,13 +837,11 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
858837
List *rels;
859838

860839
rels = OpenTableList(relations);
861-
CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
862-
PUBLICATIONOBJ_TABLE);
863-
864840
TransformPubWhereClauses(rels, pstate->p_sourcetext,
865841
publish_via_partition_root);
866842

867843
CheckPubRelationColumnList(rels, pstate->p_sourcetext,
844+
schemaidlist != NIL,
868845
publish_via_partition_root);
869846

870847
PublicationAddTables(puboid, rels, true, NULL);
@@ -1110,8 +1087,8 @@ InvalidatePublicationRels(List *relids)
11101087
*/
11111088
static void
11121089
AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
1113-
List *tables, List *schemaidlist,
1114-
const char *queryString)
1090+
List *tables, const char *queryString,
1091+
bool publish_schema)
11151092
{
11161093
List *rels = NIL;
11171094
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
@@ -1129,19 +1106,12 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
11291106

11301107
if (stmt->action == AP_AddObjects)
11311108
{
1132-
List *schemas = NIL;
1133-
1134-
/*
1135-
* Check if the relation is member of the existing schema in the
1136-
* publication or member of the schema list specified.
1137-
*/
1138-
schemas = list_concat_copy(schemaidlist, GetPublicationSchemas(pubid));
1139-
CheckObjSchemaNotAlreadyInPublication(rels, schemas,
1140-
PUBLICATIONOBJ_TABLE);
1141-
11421109
TransformPubWhereClauses(rels, queryString, pubform->pubviaroot);
11431110

1144-
CheckPubRelationColumnList(rels, queryString, pubform->pubviaroot);
1111+
publish_schema |= is_schema_publication(pubid);
1112+
1113+
CheckPubRelationColumnList(rels, queryString, publish_schema,
1114+
pubform->pubviaroot);
11451115

11461116
PublicationAddTables(pubid, rels, false, stmt);
11471117
}
@@ -1154,12 +1124,10 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
11541124
List *delrels = NIL;
11551125
ListCell *oldlc;
11561126

1157-
CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
1158-
PUBLICATIONOBJ_TABLE);
1159-
11601127
TransformPubWhereClauses(rels, queryString, pubform->pubviaroot);
11611128

1162-
CheckPubRelationColumnList(rels, queryString, pubform->pubviaroot);
1129+
CheckPubRelationColumnList(rels, queryString, publish_schema,
1130+
pubform->pubviaroot);
11631131

11641132
/*
11651133
* To recreate the relation list for the publication, look for
@@ -1308,16 +1276,35 @@ AlterPublicationSchemas(AlterPublicationStmt *stmt,
13081276
LockSchemaList(schemaidlist);
13091277
if (stmt->action == AP_AddObjects)
13101278
{
1311-
List *rels;
1279+
ListCell *lc;
13121280
List *reloids;
13131281

13141282
reloids = GetPublicationRelations(pubform->oid, PUBLICATION_PART_ROOT);
1315-
rels = OpenRelIdList(reloids);
13161283

1317-
CheckObjSchemaNotAlreadyInPublication(rels, schemaidlist,
1318-
PUBLICATIONOBJ_TABLES_IN_SCHEMA);
1284+
foreach(lc, reloids)
1285+
{
1286+
HeapTuple coltuple;
1287+
1288+
coltuple = SearchSysCache2(PUBLICATIONRELMAP,
1289+
ObjectIdGetDatum(lfirst_oid(lc)),
1290+
ObjectIdGetDatum(pubform->oid));
1291+
1292+
if (!HeapTupleIsValid(coltuple))
1293+
continue;
1294+
1295+
/*
1296+
* Disallow adding schema if column list is already part of the
1297+
* publication. See CheckPubRelationColumnList.
1298+
*/
1299+
if (!heap_attisnull(coltuple, Anum_pg_publication_rel_prattrs, NULL))
1300+
ereport(ERROR,
1301+
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1302+
errmsg("cannot add schema to the publication"),
1303+
errdetail("Schema cannot be added if any table that specifies column list is already part of the publication."));
1304+
1305+
ReleaseSysCache(coltuple);
1306+
}
13191307

1320-
CloseTableList(rels);
13211308
PublicationAddSchemas(pubform->oid, schemaidlist, false, stmt);
13221309
}
13231310
else if (stmt->action == AP_DropObjects)
@@ -1429,14 +1416,7 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
14291416

14301417
heap_freetuple(tup);
14311418

1432-
/*
1433-
* Lock the publication so nobody else can do anything with it. This
1434-
* prevents concurrent alter to add table(s) that were already going
1435-
* to become part of the publication by adding corresponding schema(s)
1436-
* via this command and similarly it will prevent the concurrent
1437-
* addition of schema(s) for which there is any corresponding table
1438-
* being added by this command.
1439-
*/
1419+
/* Lock the publication so nobody else can do anything with it. */
14401420
LockDatabaseObject(PublicationRelationId, pubid, 0,
14411421
AccessExclusiveLock);
14421422

@@ -1453,8 +1433,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
14531433
errmsg("publication \"%s\" does not exist",
14541434
stmt->pubname));
14551435

1456-
AlterPublicationTables(stmt, tup, relations, schemaidlist,
1457-
pstate->p_sourcetext);
1436+
AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
1437+
schemaidlist != NIL);
14581438
AlterPublicationSchemas(stmt, tup, schemaidlist);
14591439
}
14601440

@@ -1569,32 +1549,6 @@ RemovePublicationSchemaById(Oid psoid)
15691549
table_close(rel, RowExclusiveLock);
15701550
}
15711551

1572-
/*
1573-
* Open relations specified by a relid list.
1574-
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to
1575-
* add them to a publication.
1576-
*/
1577-
static List *
1578-
OpenRelIdList(List *relids)
1579-
{
1580-
ListCell *lc;
1581-
List *rels = NIL;
1582-
1583-
foreach(lc, relids)
1584-
{
1585-
PublicationRelInfo *pub_rel;
1586-
Oid relid = lfirst_oid(lc);
1587-
Relation rel = table_open(relid,
1588-
ShareUpdateExclusiveLock);
1589-
1590-
pub_rel = palloc(sizeof(PublicationRelInfo));
1591-
pub_rel->relation = rel;
1592-
rels = lappend(rels, pub_rel);
1593-
}
1594-
1595-
return rels;
1596-
}
1597-
15981552
/*
15991553
* Open relations specified by a PublicationTable list.
16001554
* The returned tables are locked in ShareUpdateExclusiveLock mode in order to

0 commit comments

Comments
 (0)