Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit87ce27d

Browse files
author
Amit Kapila
committed
Ensure stored generated columns must be published when required.
Ensure stored generated columns that are part of REPLICA IDENTITY must bepublished explicitly for UPDATE and DELETE operations to be published. Wecan publish generated columns by listing them in the column list or byenabling the publish_generated_columns option.This commit changes the behavior of the test added in commitadedf54 bygiving an ERROR for the UPDATE operation in such cases. There is no way totrigger the bug reported in commitadedf54 but we didn't remove thecorresponding code change because it is still relevant when replicatingchanges from a publisher with version less than 18.We decided not to backpatch this behavior change to avoid the risk ofbreaking existing output plugins that may be sending generated columns bydefault although we are not aware of any such plugin. Also, we didn't seeany reports related to this on STABLE branches which is another reason notto backpatch this change.Author: Shlok Kyal, Hou ZhijieReviewed-by: Vignesh C, Amit KapilaDiscussion:https://postgr.es/m/CANhcyEVw4V2Awe2AB6i0E5AJLNdASShGfdBLbUd1XtWDboymCA@mail.gmail.com
1 parent77c189c commit87ce27d

File tree

9 files changed

+243
-100
lines changed

9 files changed

+243
-100
lines changed

‎doc/src/sgml/ref/create_publication.sgml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,14 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
311311
system columns.
312312
</para>
313313

314+
<para>
315+
The generated columns that are part of <literal>REPLICA IDENTITY</literal>
316+
must be published explicitly either by listing them in the column list or
317+
by enabling the <literal>publish_generated_columns</literal> option, in
318+
order for <command>UPDATE</command> and <command>DELETE</command> operations
319+
to be published.
320+
</para>
321+
314322
<para>
315323
The row filter on a table becomes redundant if
316324
<literal>FOR TABLES IN SCHEMA</literal> is specified and the table

‎src/backend/commands/publicationcmds.c

Lines changed: 91 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -336,21 +336,36 @@ pub_rf_contains_invalid_column(Oid pubid, Relation relation, List *ancestors,
336336
}
337337

338338
/*
339-
* Check if all columns referenced in the REPLICA IDENTITY are covered by
340-
* the column list.
339+
* Check for invalid columns in the publication table definition.
341340
*
342-
* Returns true if any replica identity column is not covered by column list.
341+
* This function evaluates two conditions:
342+
*
343+
* 1. Ensures that all columns referenced in the REPLICA IDENTITY are covered
344+
* by the column list. If any column is missing, *invalid_column_list is set
345+
* to true.
346+
* 2. Ensures that all the generated columns referenced in the REPLICA IDENTITY
347+
* are published either by listing them in the column list or by enabling
348+
* publish_generated_columns option. If any unpublished generated column is
349+
* found, *invalid_gen_col is set to true.
350+
*
351+
* Returns true if any of the above conditions are not met.
343352
*/
344353
bool
345-
pub_collist_contains_invalid_column(Oidpubid,Relationrelation,List*ancestors,
346-
boolpubviaroot)
354+
pub_contains_invalid_column(Oidpubid,Relationrelation,List*ancestors,
355+
boolpubviaroot,boolpubgencols,
356+
bool*invalid_column_list,
357+
bool*invalid_gen_col)
347358
{
348-
HeapTupletuple;
349359
Oidrelid=RelationGetRelid(relation);
350360
Oidpublish_as_relid=RelationGetRelid(relation);
351-
boolresult= false;
352-
Datumdatum;
353-
boolisnull;
361+
Bitmapset*idattrs;
362+
Bitmapset*columns=NULL;
363+
TupleDescdesc=RelationGetDescr(relation);
364+
Publication*pub;
365+
intx;
366+
367+
*invalid_column_list= false;
368+
*invalid_gen_col= false;
354369

355370
/*
356371
* For a partition, if pubviaroot is true, find the topmost ancestor that
@@ -368,80 +383,91 @@ pub_collist_contains_invalid_column(Oid pubid, Relation relation, List *ancestor
368383
publish_as_relid=relid;
369384
}
370385

371-
tuple=SearchSysCache2(PUBLICATIONRELMAP,
372-
ObjectIdGetDatum(publish_as_relid),
373-
ObjectIdGetDatum(pubid));
386+
/* Fetch the column list */
387+
pub=GetPublication(pubid);
388+
check_and_fetch_column_list(pub,publish_as_relid,NULL,&columns);
374389

375-
if (!HeapTupleIsValid(tuple))
376-
return false;
390+
if (relation->rd_rel->relreplident==REPLICA_IDENTITY_FULL)
391+
{
392+
/* With REPLICA IDENTITY FULL, no column list is allowed. */
393+
*invalid_column_list= (columns!=NULL);
377394

378-
datum=SysCacheGetAttr(PUBLICATIONRELMAP,tuple,
379-
Anum_pg_publication_rel_prattrs,
380-
&isnull);
395+
/*
396+
* As we don't allow a column list with REPLICA IDENTITY FULL, the
397+
* publish_generated_columns option must be set to true if the table
398+
* has any stored generated columns.
399+
*/
400+
if (!pubgencols&&
401+
relation->rd_att->constr&&
402+
relation->rd_att->constr->has_generated_stored)
403+
*invalid_gen_col= true;
381404

382-
if (!isnull)
383-
{
384-
intx;
385-
Bitmapset*idattrs;
386-
Bitmapset*columns=NULL;
405+
if (*invalid_gen_col&&*invalid_column_list)
406+
return true;
407+
}
387408

388-
/*With REPLICA IDENTITY FULL, no column list is allowed. */
389-
if (relation->rd_rel->relreplident==REPLICA_IDENTITY_FULL)
390-
result= true;
409+
/*Remember columns that are part of the REPLICA IDENTITY */
410+
idattrs=RelationGetIndexAttrBitmap(relation,
411+
INDEX_ATTR_BITMAP_IDENTITY_KEY);
391412

392-
/* Transform the column list datum to a bitmapset. */
393-
columns=pub_collist_to_bitmapset(NULL,datum,NULL);
413+
/*
414+
* Attnums in the bitmap returned by RelationGetIndexAttrBitmap are offset
415+
* (to handle system columns the usual way), while column list does not
416+
* use offset, so we can't do bms_is_subset(). Instead, we have to loop
417+
* over the idattrs and check all of them are in the list.
418+
*/
419+
x=-1;
420+
while ((x=bms_next_member(idattrs,x)) >=0)
421+
{
422+
AttrNumberattnum= (x+FirstLowInvalidHeapAttributeNumber);
423+
Form_pg_attributeatt=TupleDescAttr(desc,attnum-1);
394424

395-
/* Remember columns that are part of the REPLICA IDENTITY */
396-
idattrs=RelationGetIndexAttrBitmap(relation,
397-
INDEX_ATTR_BITMAP_IDENTITY_KEY);
425+
if (columns==NULL)
426+
{
427+
/*
428+
* The publish_generated_columns option must be set to true if the
429+
* REPLICA IDENTITY contains any stored generated column.
430+
*/
431+
if (!pubgencols&&att->attgenerated)
432+
{
433+
*invalid_gen_col= true;
434+
break;
435+
}
436+
437+
/* Skip validating the column list since it is not defined */
438+
continue;
439+
}
398440

399441
/*
400-
*Attnums in the bitmap returned by RelationGetIndexAttrBitmap are
401-
*offset (to handle system columnstheusual way), while column list
402-
*does not use offset, so we can't do bms_is_subset(). Instead, we
403-
*have to loop overtheidattrs and check all of them are in the
404-
*list.
442+
*If pubviaroot is true, we are validating the column list of the
443+
*parent table, butthebitmap contains the replica identity
444+
*information of the child table. The parent/child attnums may not
445+
*match, so translate them totheparent - get the attname from the
446+
*child, and look it up in the parent.
405447
*/
406-
x=-1;
407-
while ((x=bms_next_member(idattrs,x)) >=0)
448+
if (pubviaroot)
408449
{
409-
AttrNumberattnum= (x+FirstLowInvalidHeapAttributeNumber);
450+
/* attribute name in the child table */
451+
char*colname=get_attname(relid,attnum, false);
410452

411453
/*
412-
* If pubviaroot is true, we are validating the column list of the
413-
* parent table, but the bitmap contains the replica identity
414-
* information of the child table. The parent/child attnums may
415-
* not match, so translate them to the parent - get the attname
416-
* from the child, and look it up in the parent.
454+
* Determine the attnum for the attribute name in parent (we are
455+
* using the column list defined on the parent).
417456
*/
418-
if (pubviaroot)
419-
{
420-
/* attribute name in the child table */
421-
char*colname=get_attname(relid,attnum, false);
422-
423-
/*
424-
* Determine the attnum for the attribute name in parent (we
425-
* are using the column list defined on the parent).
426-
*/
427-
attnum=get_attnum(publish_as_relid,colname);
428-
}
429-
430-
/* replica identity column, not covered by the column list */
431-
if (!bms_is_member(attnum,columns))
432-
{
433-
result= true;
434-
break;
435-
}
457+
attnum=get_attnum(publish_as_relid,colname);
436458
}
437459

438-
bms_free(idattrs);
439-
bms_free(columns);
460+
/* replica identity column, not covered by the column list */
461+
*invalid_column_list |= !bms_is_member(attnum,columns);
462+
463+
if (*invalid_column_list&&*invalid_gen_col)
464+
break;
440465
}
441466

442-
ReleaseSysCache(tuple);
467+
bms_free(columns);
468+
bms_free(idattrs);
443469

444-
returnresult;
470+
return*invalid_column_list||*invalid_gen_col;
445471
}
446472

447473
/* check_functions_in_node callback */

‎src/backend/executor/execReplication.c

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -785,16 +785,27 @@ CheckCmdReplicaIdentity(Relation rel, CmdType cmd)
785785
return;
786786

787787
/*
788-
* It is only safe to execute UPDATE/DELETE when all columns, referenced
789-
* in the row filters from publications which the relation is in, are
790-
* valid - i.e. when all referenced columns are part of REPLICA IDENTITY
791-
* or the table does not publish UPDATEs or DELETEs.
788+
* It is only safe to execute UPDATE/DELETE if the relation does not
789+
* publish UPDATEs or DELETEs, or all the following conditions are
790+
* satisfied:
791+
*
792+
* 1. All columns, referenced in the row filters from publications which
793+
* the relation is in, are valid - i.e. when all referenced columns are
794+
* part of REPLICA IDENTITY.
795+
*
796+
* 2. All columns, referenced in the column lists are valid - i.e. when
797+
* all columns referenced in the REPLICA IDENTITY are covered by the
798+
* column list.
799+
*
800+
* 3. All generated columns in REPLICA IDENTITY of the relation, are valid
801+
* - i.e. when all these generated columns are published.
792802
*
793803
* XXX We could optimize it by first checking whether any of the
794-
* publications have a row filter for this relation. If not and relation
795-
* has replica identity then we can avoid building the descriptor but as
796-
* this happens only one time it doesn't seem worth the additional
797-
* complexity.
804+
* publications have a row filter or column list for this relation, or if
805+
* the relation contains a generated column. If none of these exist and
806+
* the relation has replica identity then we can avoid building the
807+
* descriptor but as this happens only one time it doesn't seem worth the
808+
* additional complexity.
798809
*/
799810
RelationBuildPublicationDesc(rel,&pubdesc);
800811
if (cmd==CMD_UPDATE&& !pubdesc.rf_valid_for_update)
@@ -809,6 +820,12 @@ CheckCmdReplicaIdentity(Relation rel, CmdType cmd)
809820
errmsg("cannot update table \"%s\"",
810821
RelationGetRelationName(rel)),
811822
errdetail("Column list used by the publication does not cover the replica identity.")));
823+
elseif (cmd==CMD_UPDATE&& !pubdesc.gencols_valid_for_update)
824+
ereport(ERROR,
825+
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
826+
errmsg("cannot update table \"%s\"",
827+
RelationGetRelationName(rel)),
828+
errdetail("Replica identity consists of an unpublished generated column.")));
812829
elseif (cmd==CMD_DELETE&& !pubdesc.rf_valid_for_delete)
813830
ereport(ERROR,
814831
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
@@ -821,6 +838,12 @@ CheckCmdReplicaIdentity(Relation rel, CmdType cmd)
821838
errmsg("cannot delete from table \"%s\"",
822839
RelationGetRelationName(rel)),
823840
errdetail("Column list used by the publication does not cover the replica identity.")));
841+
elseif (cmd==CMD_DELETE&& !pubdesc.gencols_valid_for_delete)
842+
ereport(ERROR,
843+
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
844+
errmsg("cannot delete from table \"%s\"",
845+
RelationGetRelationName(rel)),
846+
errdetail("Replica identity consists of an unpublished generated column.")));
824847

825848
/* If relation has replica identity we are always good. */
826849
if (OidIsValid(RelationGetReplicaIndex(rel)))

‎src/backend/utils/cache/relcache.c

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5706,12 +5706,19 @@ RelationGetExclusionInfo(Relation indexRelation,
57065706
* Get the publication information for the given relation.
57075707
*
57085708
* Traverse all the publications which the relation is in to get the
5709-
* publication actions and validate the row filter expressions for such
5710-
* publications if any. We consider the row filter expression as invalid if it
5711-
* references any column which is not part of REPLICA IDENTITY.
5709+
* publication actions and validate:
5710+
* 1. The row filter expressions for such publications if any. We consider the
5711+
* row filter expression as invalid if it references any column which is not
5712+
* part of REPLICA IDENTITY.
5713+
* 2. The column list for such publication if any. We consider the column list
5714+
* invalid if REPLICA IDENTITY contains any column that is not part of it.
5715+
* 3. The generated columns of the relation for such publications. We consider
5716+
* any reference of an unpublished generated column in REPLICA IDENTITY as
5717+
* invalid.
57125718
*
57135719
* To avoid fetching the publication information repeatedly, we cache the
5714-
* publication actions and row filter validation information.
5720+
* publication actions, row filter validation information, column list
5721+
* validation information, and generated column validation information.
57155722
*/
57165723
void
57175724
RelationBuildPublicationDesc(Relationrelation,PublicationDesc*pubdesc)
@@ -5734,6 +5741,8 @@ RelationBuildPublicationDesc(Relation relation, PublicationDesc *pubdesc)
57345741
pubdesc->rf_valid_for_delete= true;
57355742
pubdesc->cols_valid_for_update= true;
57365743
pubdesc->cols_valid_for_delete= true;
5744+
pubdesc->gencols_valid_for_update= true;
5745+
pubdesc->gencols_valid_for_delete= true;
57375746
return;
57385747
}
57395748

@@ -5748,6 +5757,8 @@ RelationBuildPublicationDesc(Relation relation, PublicationDesc *pubdesc)
57485757
pubdesc->rf_valid_for_delete= true;
57495758
pubdesc->cols_valid_for_update= true;
57505759
pubdesc->cols_valid_for_delete= true;
5760+
pubdesc->gencols_valid_for_update= true;
5761+
pubdesc->gencols_valid_for_delete= true;
57515762

57525763
/* Fetch the publication membership info. */
57535764
puboids=GetRelationPublications(relid);
@@ -5777,6 +5788,8 @@ RelationBuildPublicationDesc(Relation relation, PublicationDesc *pubdesc)
57775788
Oidpubid=lfirst_oid(lc);
57785789
HeapTupletup;
57795790
Form_pg_publicationpubform;
5791+
boolinvalid_column_list;
5792+
boolinvalid_gen_col;
57805793

57815794
tup=SearchSysCache1(PUBLICATIONOID,ObjectIdGetDatum(pubid));
57825795

@@ -5811,18 +5824,27 @@ RelationBuildPublicationDesc(Relation relation, PublicationDesc *pubdesc)
58115824
/*
58125825
* Check if all columns are part of the REPLICA IDENTITY index or not.
58135826
*
5814-
*If the publication is FOR ALL TABLES then it meansthetable has no
5815-
*column list and we can skip the validation.
5827+
*Check if all generated columns included intheREPLICA IDENTITY are
5828+
*published.
58165829
*/
5817-
if (!pubform->puballtables&&
5818-
(pubform->pubupdate||pubform->pubdelete)&&
5819-
pub_collist_contains_invalid_column(pubid,relation,ancestors,
5820-
pubform->pubviaroot))
5830+
if ((pubform->pubupdate||pubform->pubdelete)&&
5831+
pub_contains_invalid_column(pubid,relation,ancestors,
5832+
pubform->pubviaroot,
5833+
pubform->pubgencols,
5834+
&invalid_column_list,
5835+
&invalid_gen_col))
58215836
{
58225837
if (pubform->pubupdate)
5823-
pubdesc->cols_valid_for_update= false;
5838+
{
5839+
pubdesc->cols_valid_for_update= !invalid_column_list;
5840+
pubdesc->gencols_valid_for_update= !invalid_gen_col;
5841+
}
5842+
58245843
if (pubform->pubdelete)
5825-
pubdesc->cols_valid_for_delete= false;
5844+
{
5845+
pubdesc->cols_valid_for_delete= !invalid_column_list;
5846+
pubdesc->gencols_valid_for_delete= !invalid_gen_col;
5847+
}
58265848
}
58275849

58285850
ReleaseSysCache(tup);
@@ -5846,6 +5868,17 @@ RelationBuildPublicationDesc(Relation relation, PublicationDesc *pubdesc)
58465868
pubdesc->pubactions.pubdelete&&pubdesc->pubactions.pubtruncate&&
58475869
!pubdesc->cols_valid_for_update&& !pubdesc->cols_valid_for_delete)
58485870
break;
5871+
5872+
/*
5873+
* If we know everything is replicated and replica identity has an
5874+
* unpublished generated column, there is no point to check for other
5875+
* publications.
5876+
*/
5877+
if (pubdesc->pubactions.pubinsert&&pubdesc->pubactions.pubupdate&&
5878+
pubdesc->pubactions.pubdelete&&pubdesc->pubactions.pubtruncate&&
5879+
!pubdesc->gencols_valid_for_update&&
5880+
!pubdesc->gencols_valid_for_delete)
5881+
break;
58495882
}
58505883

58515884
if (relation->rd_pubdesc)

‎src/include/catalog/pg_publication.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,13 @@ typedef struct PublicationDesc
9898
*/
9999
boolcols_valid_for_update;
100100
boolcols_valid_for_delete;
101+
102+
/*
103+
* true if all generated columns that are part of replica identity are
104+
* published or the publication actions do not include UPDATE or DELETE.
105+
*/
106+
boolgencols_valid_for_update;
107+
boolgencols_valid_for_delete;
101108
}PublicationDesc;
102109

103110
typedefstructPublication

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp