Preserve replica identity index across ALTER TABLE rewrite
authorPeter Eisentraut <peter@eisentraut.org>
Fri, 13 Mar 2020 10:28:11 +0000 (11:28 +0100)
committerPeter Eisentraut <peter@eisentraut.org>
Fri, 13 Mar 2020 10:57:06 +0000 (11:57 +0100)
If an index was explicitly set as replica identity index, this setting
was lost when a table was rewritten by ALTER TABLE.  Because this
setting is part of pg_index but actually controlled by ALTER
TABLE (not part of CREATE INDEX, say), we have to do some extra work
to restore it.

Based-on-patch-by: Quan Zongliang <quanzongliang@gmail.com>
Reviewed-by: Euler Taveira <euler.taveira@2ndquadrant.com>
Discussion: https://www.postgresql.org/message-id/flat/c70fcab2-4866-0d9f-1d01-e75e189db342@gmail.com

src/backend/commands/tablecmds.c
src/backend/utils/cache/lsyscache.c
src/include/utils/lsyscache.h
src/test/regress/expected/replica_identity.out
src/test/regress/sql/replica_identity.sql

index 3eb861bfbf971e9a4d67d4f13c8c510b94992c7c..8c33b67c1b5ace10d51bf760deed5955280dfa47 100644 (file)
@@ -176,6 +176,7 @@ typedef struct AlteredTableInfo
    List       *changedConstraintDefs;  /* string definitions of same */
    List       *changedIndexOids;   /* OIDs of indexes to rebuild */
    List       *changedIndexDefs;   /* string definitions of same */
+   char       *replicaIdentityIndex;   /* index to reset as REPLICA IDENTITY */
 } AlteredTableInfo;
 
 /* Struct describing one new constraint to check in Phase 3 scan */
@@ -11562,6 +11563,22 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
    return address;
 }
 
+/*
+ * Subroutine for ATExecAlterColumnType: remember that a replica identity
+ * needs to be reset.
+ */
+static void
+RememberReplicaIdentityForRebuilding(Oid indoid, AlteredTableInfo *tab)
+{
+   if (!get_index_isreplident(indoid))
+       return;
+
+   if (tab->replicaIdentityIndex)
+       elog(ERROR, "relation %u has multiple indexes marked as replica identity", tab->relid);
+
+   tab->replicaIdentityIndex = get_rel_name(indoid);
+}
+
 /*
  * Subroutine for ATExecAlterColumnType: remember that a constraint needs
  * to be rebuilt (which we might already know).
@@ -11580,11 +11597,16 @@ RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab)
    {
        /* OK, capture the constraint's existing definition string */
        char       *defstring = pg_get_constraintdef_command(conoid);
+       Oid         indoid;
 
        tab->changedConstraintOids = lappend_oid(tab->changedConstraintOids,
                                                 conoid);
        tab->changedConstraintDefs = lappend(tab->changedConstraintDefs,
                                             defstring);
+
+       indoid = get_constraint_index(conoid);
+       if (OidIsValid(indoid))
+           RememberReplicaIdentityForRebuilding(indoid, tab);
    }
 }
 
@@ -11627,6 +11649,8 @@ RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab)
                                                indoid);
            tab->changedIndexDefs = lappend(tab->changedIndexDefs,
                                            defstring);
+
+           RememberReplicaIdentityForRebuilding(indoid, tab);
        }
    }
 }
@@ -11735,6 +11759,24 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
        add_exact_object_address(&obj, objects);
    }
 
+   /*
+    * Queue up command to restore replica identity index marking
+    */
+   if (tab->replicaIdentityIndex)
+   {
+       AlterTableCmd *cmd = makeNode(AlterTableCmd);
+       ReplicaIdentityStmt *subcmd = makeNode(ReplicaIdentityStmt);
+
+       subcmd->identity_type = REPLICA_IDENTITY_INDEX;
+       subcmd->name = tab->replicaIdentityIndex;
+       cmd->subtype = AT_ReplicaIdentity;
+       cmd->def = (Node *) subcmd;
+
+       /* do it after indexes and constraints */
+       tab->subcmds[AT_PASS_OLD_CONSTR] =
+           lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
+   }
+
    /*
     * It should be okay to use DROP_RESTRICT here, since nothing else should
     * be depending on these objects.
index 6b24369de8ff9b02740ad13dd5a4927c3c1f8ba3..27bbb58f5646ab7b2bd27bfcdb68d1e161677c7e 100644 (file)
@@ -3254,6 +3254,29 @@ get_index_column_opclass(Oid index_oid, int attno)
    return opclass;
 }
 
+/*
+ * get_index_isreplident
+ *
+ *     Given the index OID, return pg_index.indisreplident.
+ */
+bool
+get_index_isreplident(Oid index_oid)
+{
+   HeapTuple       tuple;
+   Form_pg_index   rd_index;
+   bool            result;
+
+   tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid));
+   if (!HeapTupleIsValid(tuple))
+       return false;
+
+   rd_index = (Form_pg_index) GETSTRUCT(tuple);
+   result = rd_index->indisreplident;
+   ReleaseSysCache(tuple);
+
+   return result;
+}
+
 /*
  * get_index_isvalid
  *
index 6e93590befcd044aaf9d0b698ff93947c839d055..4e646c55e90ebfc07835f30513e14d103fa8c31e 100644 (file)
@@ -182,6 +182,7 @@ extern char *get_namespace_name_or_temp(Oid nspid);
 extern Oid get_range_subtype(Oid rangeOid);
 extern Oid get_range_collation(Oid rangeOid);
 extern Oid get_index_column_opclass(Oid index_oid, int attno);
+extern bool    get_index_isreplident(Oid index_oid);
 extern bool get_index_isvalid(Oid index_oid);
 
 #define type_is_array(typid)  (get_element_type(typid) != InvalidOid)
index 739608aab4c73aad58906ee98f680464f66c2f29..79002197a7af688a117d8f15fa0b4e9e277b577e 100644 (file)
@@ -179,5 +179,51 @@ SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
  n
 (1 row)
 
+---
+-- Test that ALTER TABLE rewrite preserves nondefault replica identity
+---
+-- constraint variant
+CREATE TABLE test_replica_identity2 (id int UNIQUE NOT NULL);
+ALTER TABLE test_replica_identity2 REPLICA IDENTITY USING INDEX test_replica_identity2_id_key;
+\d test_replica_identity2
+       Table "public.test_replica_identity2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ id     | integer |           | not null | 
+Indexes:
+    "test_replica_identity2_id_key" UNIQUE CONSTRAINT, btree (id) REPLICA IDENTITY
+
+ALTER TABLE test_replica_identity2 ALTER COLUMN id TYPE bigint;
+\d test_replica_identity2
+      Table "public.test_replica_identity2"
+ Column |  Type  | Collation | Nullable | Default 
+--------+--------+-----------+----------+---------
+ id     | bigint |           | not null | 
+Indexes:
+    "test_replica_identity2_id_key" UNIQUE CONSTRAINT, btree (id) REPLICA IDENTITY
+
+-- straight index variant
+CREATE TABLE test_replica_identity3 (id int NOT NULL);
+CREATE UNIQUE INDEX test_replica_identity3_id_key ON test_replica_identity3 (id);
+ALTER TABLE test_replica_identity3 REPLICA IDENTITY USING INDEX test_replica_identity3_id_key;
+\d test_replica_identity3
+       Table "public.test_replica_identity3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ id     | integer |           | not null | 
+Indexes:
+    "test_replica_identity3_id_key" UNIQUE, btree (id) REPLICA IDENTITY
+
+ALTER TABLE test_replica_identity3 ALTER COLUMN id TYPE bigint;
+\d test_replica_identity3
+      Table "public.test_replica_identity3"
+ Column |  Type  | Collation | Nullable | Default 
+--------+--------+-----------+----------+---------
+ id     | bigint |           | not null | 
+Indexes:
+    "test_replica_identity3_id_key" UNIQUE, btree (id) REPLICA IDENTITY
+
 DROP TABLE test_replica_identity;
+DROP TABLE test_replica_identity2;
+DROP TABLE test_replica_identity3;
 DROP TABLE test_replica_identity_othertable;
index b08a3623b8ce0a31dc93eaa887bd2e77d96bcdcc..a5ac8f55673abd5fb716eae05275f1cc52edfa4c 100644 (file)
@@ -75,5 +75,26 @@ SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
 ALTER TABLE test_replica_identity REPLICA IDENTITY NOTHING;
 SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
 
+---
+-- Test that ALTER TABLE rewrite preserves nondefault replica identity
+---
+
+-- constraint variant
+CREATE TABLE test_replica_identity2 (id int UNIQUE NOT NULL);
+ALTER TABLE test_replica_identity2 REPLICA IDENTITY USING INDEX test_replica_identity2_id_key;
+\d test_replica_identity2
+ALTER TABLE test_replica_identity2 ALTER COLUMN id TYPE bigint;
+\d test_replica_identity2
+
+-- straight index variant
+CREATE TABLE test_replica_identity3 (id int NOT NULL);
+CREATE UNIQUE INDEX test_replica_identity3_id_key ON test_replica_identity3 (id);
+ALTER TABLE test_replica_identity3 REPLICA IDENTITY USING INDEX test_replica_identity3_id_key;
+\d test_replica_identity3
+ALTER TABLE test_replica_identity3 ALTER COLUMN id TYPE bigint;
+\d test_replica_identity3
+
 DROP TABLE test_replica_identity;
+DROP TABLE test_replica_identity2;
+DROP TABLE test_replica_identity3;
 DROP TABLE test_replica_identity_othertable;