Skip to content

Commit a5f3f2f

Browse files
committed
Allow REPLICA IDENTITY to be set on an index that's not (yet) valid.
The motivation for this change is that when pg_dump dumps a partitioned index that's marked REPLICA IDENTITY, it generates a command sequence that applies REPLICA IDENTITY before the partitioned index has been marked valid, causing restore to fail. We could perhaps change pg_dump to not do it like that, but that would be difficult and would not fix existing dump files with the problem. There seems to be very little reason for the backend to disallow this anyway --- the code ignores indisreplident when the index isn't valid --- so instead let's fix it by allowing the case. Commit 9511fb3 previously expressed a concern that allowing indisreplident to be set on invalid indexes might allow us to wind up in a situation where a table could have indisreplident set on multiple indexes. I'm not sure I follow that concern exactly, but in any case the only way that could happen is because relation_mark_replica_identity is too trusting about the existing set of markings being valid. Let's just rip out its early-exit code path (which sure looks like premature optimization anyway; what are we doing expending code to make redundant ALTER TABLE ... REPLICA IDENTITY commands marginally faster and not-redundant ones marginally slower?) and fix it to positively guarantee that no more than one index is marked indisreplident. The pg_dump failure can be demonstrated in all supported branches, so back-patch all the way. I chose to back-patch 9511fb3 as well, just to keep indisreplident handling the same in all branches. Per bug #17756 from Sergey Belyashov. Discussion: https://postgr.es/m/17756-dd50e8e0c8dd4a40@postgresql.org
1 parent e75b5c8 commit a5f3f2f

File tree

4 files changed

+85
-44
lines changed

4 files changed

+85
-44
lines changed

src/backend/catalog/index.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1547,7 +1547,6 @@ index_concurrently_swap(Oid newIndexId, Oid oldIndexId, const char *oldName)
15471547

15481548
/* Preserve indisreplident in the new index */
15491549
newIndexForm->indisreplident = oldIndexForm->indisreplident;
1550-
oldIndexForm->indisreplident = false;
15511550

15521551
/* Preserve indisclustered in the new index */
15531552
newIndexForm->indisclustered = oldIndexForm->indisclustered;
@@ -1559,6 +1558,7 @@ index_concurrently_swap(Oid newIndexId, Oid oldIndexId, const char *oldName)
15591558
newIndexForm->indisvalid = true;
15601559
oldIndexForm->indisvalid = false;
15611560
oldIndexForm->indisclustered = false;
1561+
oldIndexForm->indisreplident = false;
15621562

15631563
CatalogTupleUpdate(pg_index, &oldIndexTuple->t_self, oldIndexTuple);
15641564
CatalogTupleUpdate(pg_index, &newIndexTuple->t_self, newIndexTuple);
@@ -3462,10 +3462,12 @@ index_set_state_flags(Oid indexId, IndexStateFlagsAction action)
34623462
* CONCURRENTLY that failed partway through.)
34633463
*
34643464
* Note: the CLUSTER logic assumes that indisclustered cannot be
3465-
* set on any invalid index, so clear that flag too.
3465+
* set on any invalid index, so clear that flag too. For
3466+
* cleanliness, also clear indisreplident.
34663467
*/
34673468
indexForm->indisvalid = false;
34683469
indexForm->indisclustered = false;
3470+
indexForm->indisreplident = false;
34693471
break;
34703472
case INDEX_DROP_SET_DEAD:
34713473

@@ -3477,6 +3479,8 @@ index_set_state_flags(Oid indexId, IndexStateFlagsAction action)
34773479
* the index at all.
34783480
*/
34793481
Assert(!indexForm->indisvalid);
3482+
Assert(!indexForm->indisclustered);
3483+
Assert(!indexForm->indisreplident);
34803484
indexForm->indisready = false;
34813485
indexForm->indislive = false;
34823486
break;

src/backend/commands/tablecmds.c

Lines changed: 22 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -14411,7 +14411,10 @@ ATExecDropOf(Relation rel, LOCKMODE lockmode)
1441114411
* relation_mark_replica_identity: Update a table's replica identity
1441214412
*
1441314413
* Iff ri_type = REPLICA_IDENTITY_INDEX, indexOid must be the Oid of a suitable
14414-
* index. Otherwise, it should be InvalidOid.
14414+
* index. Otherwise, it must be InvalidOid.
14415+
*
14416+
* Caller had better hold an exclusive lock on the relation, as the results
14417+
* of running two of these concurrently wouldn't be pretty.
1441514418
*/
1441614419
static void
1441714420
relation_mark_replica_identity(Relation rel, char ri_type, Oid indexOid,
@@ -14423,7 +14426,6 @@ relation_mark_replica_identity(Relation rel, char ri_type, Oid indexOid,
1442314426
HeapTuple pg_index_tuple;
1442414427
Form_pg_class pg_class_form;
1442514428
Form_pg_index pg_index_form;
14426-
1442714429
ListCell *index;
1442814430

1442914431
/*
@@ -14445,29 +14447,7 @@ relation_mark_replica_identity(Relation rel, char ri_type, Oid indexOid,
1444514447
heap_freetuple(pg_class_tuple);
1444614448

1444714449
/*
14448-
* Check whether the correct index is marked indisreplident; if so, we're
14449-
* done.
14450-
*/
14451-
if (OidIsValid(indexOid))
14452-
{
14453-
Assert(ri_type == REPLICA_IDENTITY_INDEX);
14454-
14455-
pg_index_tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexOid));
14456-
if (!HeapTupleIsValid(pg_index_tuple))
14457-
elog(ERROR, "cache lookup failed for index %u", indexOid);
14458-
pg_index_form = (Form_pg_index) GETSTRUCT(pg_index_tuple);
14459-
14460-
if (pg_index_form->indisreplident)
14461-
{
14462-
ReleaseSysCache(pg_index_tuple);
14463-
return;
14464-
}
14465-
ReleaseSysCache(pg_index_tuple);
14466-
}
14467-
14468-
/*
14469-
* Clear the indisreplident flag from any index that had it previously,
14470-
* and set it for any index that should have it now.
14450+
* Update the per-index indisreplident flags correctly.
1447114451
*/
1447214452
pg_index = table_open(IndexRelationId, RowExclusiveLock);
1447314453
foreach(index, RelationGetIndexList(rel))
@@ -14481,19 +14461,23 @@ relation_mark_replica_identity(Relation rel, char ri_type, Oid indexOid,
1448114461
elog(ERROR, "cache lookup failed for index %u", thisIndexOid);
1448214462
pg_index_form = (Form_pg_index) GETSTRUCT(pg_index_tuple);
1448314463

14484-
/*
14485-
* Unset the bit if set. We know it's wrong because we checked this
14486-
* earlier.
14487-
*/
14488-
if (pg_index_form->indisreplident)
14464+
if (thisIndexOid == indexOid)
1448914465
{
14490-
dirty = true;
14491-
pg_index_form->indisreplident = false;
14466+
/* Set the bit if not already set. */
14467+
if (!pg_index_form->indisreplident)
14468+
{
14469+
dirty = true;
14470+
pg_index_form->indisreplident = true;
14471+
}
1449214472
}
14493-
else if (thisIndexOid == indexOid)
14473+
else
1449414474
{
14495-
dirty = true;
14496-
pg_index_form->indisreplident = true;
14475+
/* Unset the bit if set. */
14476+
if (pg_index_form->indisreplident)
14477+
{
14478+
dirty = true;
14479+
pg_index_form->indisreplident = false;
14480+
}
1449714481
}
1449814482

1449914483
if (dirty)
@@ -14504,7 +14488,9 @@ relation_mark_replica_identity(Relation rel, char ri_type, Oid indexOid,
1450414488
/*
1450514489
* Invalidate the relcache for the table, so that after we commit
1450614490
* all sessions will refresh the table's replica identity index
14507-
* before attempting any UPDATE or DELETE on the table.
14491+
* before attempting any UPDATE or DELETE on the table. (If we
14492+
* changed the table's pg_class row above, then a relcache inval
14493+
* is already queued due to that; but we might not have.)
1450814494
*/
1450914495
CacheInvalidateRelcache(rel);
1451014496
}
@@ -14590,12 +14576,6 @@ ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode
1459014576
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1459114577
errmsg("cannot use partial index \"%s\" as replica identity",
1459214578
RelationGetRelationName(indexRel))));
14593-
/* And neither are invalid indexes. */
14594-
if (!indexRel->rd_index->indisvalid)
14595-
ereport(ERROR,
14596-
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
14597-
errmsg("cannot use invalid index \"%s\" as replica identity",
14598-
RelationGetRelationName(indexRel))));
1459914579

1460014580
/* Check index for nullable columns. */
1460114581
for (key = 0; key < IndexRelationGetNumberOfKeyAttributes(indexRel); key++)

src/test/regress/expected/replica_identity.out

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,44 @@ Indexes:
227227
-- used as replica identity.
228228
ALTER TABLE test_replica_identity3 ALTER COLUMN id DROP NOT NULL;
229229
ERROR: column "id" is in index used as replica identity
230+
--
231+
-- Test that replica identity can be set on an index that's not yet valid.
232+
-- (This matches the way pg_dump will try to dump a partitioned table.)
233+
--
234+
CREATE TABLE test_replica_identity4(id integer NOT NULL) PARTITION BY LIST (id);
235+
CREATE TABLE test_replica_identity4_1(id integer NOT NULL);
236+
ALTER TABLE ONLY test_replica_identity4
237+
ATTACH PARTITION test_replica_identity4_1 FOR VALUES IN (1);
238+
ALTER TABLE ONLY test_replica_identity4
239+
ADD CONSTRAINT test_replica_identity4_pkey PRIMARY KEY (id);
240+
ALTER TABLE ONLY test_replica_identity4
241+
REPLICA IDENTITY USING INDEX test_replica_identity4_pkey;
242+
ALTER TABLE ONLY test_replica_identity4_1
243+
ADD CONSTRAINT test_replica_identity4_1_pkey PRIMARY KEY (id);
244+
\d+ test_replica_identity4
245+
Partitioned table "public.test_replica_identity4"
246+
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
247+
--------+---------+-----------+----------+---------+---------+--------------+-------------
248+
id | integer | | not null | | plain | |
249+
Partition key: LIST (id)
250+
Indexes:
251+
"test_replica_identity4_pkey" PRIMARY KEY, btree (id) INVALID REPLICA IDENTITY
252+
Partitions: test_replica_identity4_1 FOR VALUES IN (1)
253+
254+
ALTER INDEX test_replica_identity4_pkey
255+
ATTACH PARTITION test_replica_identity4_1_pkey;
256+
\d+ test_replica_identity4
257+
Partitioned table "public.test_replica_identity4"
258+
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
259+
--------+---------+-----------+----------+---------+---------+--------------+-------------
260+
id | integer | | not null | | plain | |
261+
Partition key: LIST (id)
262+
Indexes:
263+
"test_replica_identity4_pkey" PRIMARY KEY, btree (id) REPLICA IDENTITY
264+
Partitions: test_replica_identity4_1 FOR VALUES IN (1)
265+
230266
DROP TABLE test_replica_identity;
231267
DROP TABLE test_replica_identity2;
232268
DROP TABLE test_replica_identity3;
269+
DROP TABLE test_replica_identity4;
233270
DROP TABLE test_replica_identity_othertable;

src/test/regress/sql/replica_identity.sql

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,27 @@ ALTER TABLE test_replica_identity3 ALTER COLUMN id TYPE bigint;
9898
-- used as replica identity.
9999
ALTER TABLE test_replica_identity3 ALTER COLUMN id DROP NOT NULL;
100100

101+
--
102+
-- Test that replica identity can be set on an index that's not yet valid.
103+
-- (This matches the way pg_dump will try to dump a partitioned table.)
104+
--
105+
CREATE TABLE test_replica_identity4(id integer NOT NULL) PARTITION BY LIST (id);
106+
CREATE TABLE test_replica_identity4_1(id integer NOT NULL);
107+
ALTER TABLE ONLY test_replica_identity4
108+
ATTACH PARTITION test_replica_identity4_1 FOR VALUES IN (1);
109+
ALTER TABLE ONLY test_replica_identity4
110+
ADD CONSTRAINT test_replica_identity4_pkey PRIMARY KEY (id);
111+
ALTER TABLE ONLY test_replica_identity4
112+
REPLICA IDENTITY USING INDEX test_replica_identity4_pkey;
113+
ALTER TABLE ONLY test_replica_identity4_1
114+
ADD CONSTRAINT test_replica_identity4_1_pkey PRIMARY KEY (id);
115+
\d+ test_replica_identity4
116+
ALTER INDEX test_replica_identity4_pkey
117+
ATTACH PARTITION test_replica_identity4_1_pkey;
118+
\d+ test_replica_identity4
119+
101120
DROP TABLE test_replica_identity;
102121
DROP TABLE test_replica_identity2;
103122
DROP TABLE test_replica_identity3;
123+
DROP TABLE test_replica_identity4;
104124
DROP TABLE test_replica_identity_othertable;

0 commit comments

Comments
 (0)