From 31b7b4d26e10086d4a79d49a28fd161da52da49a Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sat, 22 Jan 2022 13:32:40 -0500 Subject: [PATCH] Flush table's relcache during ALTER TABLE ADD PRIMARY KEY USING INDEX. Previously, unless we had to add a NOT NULL constraint to the column, this command resulted in updating only the index's relcache entry. That's problematic when replication behavior is being driven off the existence of a primary key: other sessions (and ours too for that matter) failed to recalculate their opinion of whether the table can be replicated. Add a relcache invalidation to fix it. This has been broken since pg_class.relhaspkey was removed in v11. Before that, updating the table's relhaspkey value sufficed to cause a cache flush. Hence, backpatch to v11. Report and patch by Hou Zhijie Discussion: https://postgr.es/m/OS0PR01MB5716EBE01F112C62F8F9B786947B9@OS0PR01MB5716.jpnprd01.prod.outlook.com --- src/backend/catalog/index.c | 11 +++++++++++ src/test/regress/expected/publication.out | 14 ++++++++++++++ src/test/regress/sql/publication.sql | 13 +++++++++++++ 3 files changed, 38 insertions(+) diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 54f807af57..324828561e 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -2046,6 +2046,7 @@ index_constraint_create(Relation heapRelation, HeapTuple indexTuple; Form_pg_index indexForm; bool dirty = false; + bool marked_as_primary = false; pg_index = table_open(IndexRelationId, RowExclusiveLock); @@ -2059,6 +2060,7 @@ index_constraint_create(Relation heapRelation, { indexForm->indisprimary = true; dirty = true; + marked_as_primary = true; } if (deferrable && indexForm->indimmediate) @@ -2071,6 +2073,15 @@ index_constraint_create(Relation heapRelation, { CatalogTupleUpdate(pg_index, &indexTuple->t_self, indexTuple); + /* + * When we mark an existing index as primary, force a relcache + * flush on its parent table, so that all sessions will become + * aware that the table now has a primary key. This is important + * because it affects some replication behaviors. + */ + if (marked_as_primary) + CacheInvalidateRelcache(heapRelation); + InvokeObjectPostAlterHookArg(IndexRelationId, indexRelationId, 0, InvalidOid, is_internal); } diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out index a2aca234ef..7c7e022665 100644 --- a/src/test/regress/expected/publication.out +++ b/src/test/regress/expected/publication.out @@ -268,6 +268,20 @@ Publications: "testpib_ins_trunct" "testpub_fortbl" +-- verify relation cache invalidation when a primary key is added using +-- an existing index +CREATE TABLE pub_test.testpub_addpk (id int not null, data int); +ALTER PUBLICATION testpub_default ADD TABLE pub_test.testpub_addpk; +INSERT INTO pub_test.testpub_addpk VALUES(1, 11); +CREATE UNIQUE INDEX testpub_addpk_id_idx ON pub_test.testpub_addpk(id); +-- fail: +UPDATE pub_test.testpub_addpk SET id = 2; +ERROR: cannot update table "testpub_addpk" because it does not have a replica identity and publishes updates +HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE. +ALTER TABLE pub_test.testpub_addpk ADD PRIMARY KEY USING INDEX testpub_addpk_id_idx; +-- now it should work: +UPDATE pub_test.testpub_addpk SET id = 2; +DROP TABLE pub_test.testpub_addpk; -- permissions SET ROLE regress_publication_user2; CREATE PUBLICATION testpub2; -- fail diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql index 4f2445ad11..4b7738395e 100644 --- a/src/test/regress/sql/publication.sql +++ b/src/test/regress/sql/publication.sql @@ -149,6 +149,19 @@ ALTER PUBLICATION testpub_default DROP TABLE pub_test.testpub_nopk; \d+ testpub_tbl1 +-- verify relation cache invalidation when a primary key is added using +-- an existing index +CREATE TABLE pub_test.testpub_addpk (id int not null, data int); +ALTER PUBLICATION testpub_default ADD TABLE pub_test.testpub_addpk; +INSERT INTO pub_test.testpub_addpk VALUES(1, 11); +CREATE UNIQUE INDEX testpub_addpk_id_idx ON pub_test.testpub_addpk(id); +-- fail: +UPDATE pub_test.testpub_addpk SET id = 2; +ALTER TABLE pub_test.testpub_addpk ADD PRIMARY KEY USING INDEX testpub_addpk_id_idx; +-- now it should work: +UPDATE pub_test.testpub_addpk SET id = 2; +DROP TABLE pub_test.testpub_addpk; + -- permissions SET ROLE regress_publication_user2; CREATE PUBLICATION testpub2; -- fail