]> granicus.if.org Git - postgresql/commitdiff
Add the notion of REPLICA IDENTITY for a table.
authorRobert Haas <rhaas@postgresql.org>
Fri, 8 Nov 2013 17:30:43 +0000 (12:30 -0500)
committerRobert Haas <rhaas@postgresql.org>
Fri, 8 Nov 2013 17:30:43 +0000 (12:30 -0500)
Pending patches for logical replication will use this to determine
which columns of a tuple ought to be considered as its candidate key.

Andres Freund, with minor, mostly cosmetic adjustments by me

23 files changed:
doc/src/sgml/catalogs.sgml
doc/src/sgml/ref/alter_table.sgml
src/backend/catalog/heap.c
src/backend/catalog/index.c
src/backend/commands/tablecmds.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/parser/gram.y
src/backend/utils/cache/relcache.c
src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/pg_dump.h
src/bin/psql/describe.c
src/bin/psql/tab-complete.c
src/include/catalog/catversion.h
src/include/catalog/pg_class.h
src/include/catalog/pg_index.h
src/include/nodes/nodes.h
src/include/nodes/parsenodes.h
src/include/utils/rel.h
src/test/regress/expected/replica_identity.out [new file with mode: 0644]
src/test/regress/parallel_schedule
src/test/regress/serial_schedule
src/test/regress/sql/replica_identity.sql [new file with mode: 0644]

index 9af4697a47b5491ad7d0caad298dfde33c82e00f..9388df5ac273d32750027618e299f56ad8ba7004 100644 (file)
        relations other than some materialized views)</entry>
      </row>
 
+     <row>
+      <entry><structfield>relreplident</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>
+       Columns used to form <quote>replica identity</> for rows:
+       <literal>d</> = default (primary key, if any),
+       <literal>n</> = nothing,
+       <literal>f</> = all columns
+       <literal>i</> = index with indisreplident set, or default
+      </entry>
+     </row>
+
      <row>
       <entry><structfield>relfrozenxid</structfield></entry>
       <entry><type>xid</type></entry>
       </entry>
      </row>
 
+     <row>
+      <entry><structfield>indisreplident</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>
+       If true this index has been chosen as <quote>replica identity</>
+       using <command>ALTER TABLE ... REPLICA IDENTITY USING INDEX
+       ...</>
+      </entry>
+     </row>
+
      <row>
       <entry><structfield>indkey</structfield></entry>
       <entry><type>int2vector</type></entry>
index 2609d4a8eaf340fa2b725014ac383de3deaa36f8..89649a2aa48f4f0da5e15468ddba19365d4af697 100644 (file)
@@ -69,6 +69,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+    REPLICA IDENTITY {DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING}
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -579,6 +580,24 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>REPLICA IDENTITY</literal></term>
+    <listitem>
+     <para>
+      This form changes the information which is written to the write-ahead log
+      to identify rows which are updated or deleted.  This option has no effect
+      except when logical replication is in use.  <literal>DEFAULT</> records the 
+      old values of the columns of the primary key, if any.  <literal>USING INDEX</>
+      records the old values of the columns covered by the named index, which
+      must be unique, not partial, not deferrable, and include only columns marked
+      <literal>NOT NULL</>.  <literal>FULL</> records the old values of all columns
+      in the row.  <literal>NOTHING</> records no information about the old row.
+      In all cases, no old values are logged unless at least one of the columns
+      that would be logged differs between the old and new versions of the row.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>RENAME</literal></term>
     <listitem>
index 64ca3121b32771d7c8db30a84b81f4b6acd97574..a910f81666cf838fe570b7902482ba8010fd22b9 100644 (file)
@@ -793,6 +793,7 @@ InsertPgClassTuple(Relation pg_class_desc,
        values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
        values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
        values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
+       values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
        values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
        values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
        if (relacl != (Datum) 0)
index 826e504bb4796abe656d16ed4d354356675297e5..02752406f530148ac5767a21737b8ad070252d88 100644 (file)
@@ -614,6 +614,7 @@ UpdateIndexRelation(Oid indexoid,
        /* we set isvalid and isready the same way */
        values[Anum_pg_index_indisready - 1] = BoolGetDatum(isvalid);
        values[Anum_pg_index_indislive - 1] = BoolGetDatum(true);
+       values[Anum_pg_index_indisreplident - 1] = BoolGetDatum(false);
        values[Anum_pg_index_indkey - 1] = PointerGetDatum(indkey);
        values[Anum_pg_index_indcollation - 1] = PointerGetDatum(indcollation);
        values[Anum_pg_index_indclass - 1] = PointerGetDatum(indclass);
index 670af18f4c31a0d61a1bd2773038a3bcf7b93af3..c8fa39d025d60d69e16cf1338492acb4e35045f4 100644 (file)
@@ -399,6 +399,7 @@ static void ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid);
 static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
 static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
+static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode);
 static void ATExecGenericOptions(Relation rel, List *options);
 
 static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
@@ -2809,6 +2810,7 @@ AlterTableGetLockLevel(List *cmds)
                        case AT_DisableTrigUser:
                        case AT_AddIndex:       /* from ADD CONSTRAINT */
                        case AT_AddIndexConstraint:
+                       case AT_ReplicaIdentity:
                                cmd_lockmode = ShareRowExclusiveLock;
                                break;
 
@@ -3140,6 +3142,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
                                cmd->subtype = AT_ValidateConstraintRecurse;
                        pass = AT_PASS_MISC;
                        break;
+               case AT_ReplicaIdentity: /* REPLICA IDENTITY ... */
+                       ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
+                       pass = AT_PASS_MISC;
+                       /* This command never recurses */
+                       /* No command-specific prep needed */
+                       break;
                case AT_EnableTrig:             /* ENABLE TRIGGER variants */
                case AT_EnableAlwaysTrig:
                case AT_EnableReplicaTrig:
@@ -3440,6 +3448,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
                case AT_DropOf:
                        ATExecDropOf(rel, lockmode);
                        break;
+               case AT_ReplicaIdentity:
+                       ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
+                       break;
                case AT_GenericOptions:
                        ATExecGenericOptions(rel, (List *) cmd->def);
                        break;
@@ -10009,6 +10020,217 @@ ATExecDropOf(Relation rel, LOCKMODE lockmode)
        heap_close(relationRelation, RowExclusiveLock);
 }
 
+/*
+ * relation_mark_replica_identity: Update a table's replica identity
+ *
+ * Iff ri_type = REPLICA_IDENTITY_INDEX, indexOid must be the Oid of a suitable
+ * index. Otherwise, it should be InvalidOid.
+ */
+static void
+relation_mark_replica_identity(Relation rel, char ri_type, Oid indexOid,
+                                                          bool is_internal)
+{
+       Relation        pg_index;
+       Relation        pg_class;
+       HeapTuple       pg_class_tuple;
+       HeapTuple       pg_index_tuple;
+       Form_pg_class pg_class_form;
+       Form_pg_index pg_index_form;
+
+       ListCell   *index;
+
+       /*
+        * Check whether relreplident has changed, and update it if so.
+        */
+       pg_class = heap_open(RelationRelationId, RowExclusiveLock);
+       pg_class_tuple = SearchSysCacheCopy1(RELOID,
+                                            ObjectIdGetDatum(RelationGetRelid(rel)));
+       if (!HeapTupleIsValid(pg_class_tuple))
+               elog(ERROR, "cache lookup failed for relation \"%s\"",
+                    RelationGetRelationName(rel));
+       pg_class_form = (Form_pg_class) GETSTRUCT(pg_class_tuple);
+       if (pg_class_form->relreplident != ri_type)
+       {
+               pg_class_form->relreplident = ri_type;
+               simple_heap_update(pg_class, &pg_class_tuple->t_self, pg_class_tuple);
+               CatalogUpdateIndexes(pg_class, pg_class_tuple);
+       }
+       heap_close(pg_class, RowExclusiveLock);
+       heap_freetuple(pg_class_tuple);
+
+       /*
+        * Check whether the correct index is marked indisreplident; if so, we're
+        * done.
+        */
+       if (OidIsValid(indexOid))
+       {
+               Assert(ri_type == REPLICA_IDENTITY_INDEX);
+
+               pg_index_tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexOid));
+               if (!HeapTupleIsValid(pg_index_tuple))
+                       elog(ERROR, "cache lookup failed for index %u", indexOid);
+               pg_index_form = (Form_pg_index) GETSTRUCT(pg_index_tuple);
+
+               if (pg_index_form->indisreplident)
+               {
+                       ReleaseSysCache(pg_index_tuple);
+                       return;
+               }
+               ReleaseSysCache(pg_index_tuple);
+       }
+
+       /*
+        * Clear the indisreplident flag from any index that had it previously, and
+        * set it for any index that should have it now.
+        */
+       pg_index = heap_open(IndexRelationId, RowExclusiveLock);
+       foreach(index, RelationGetIndexList(rel))
+       {
+               Oid                     thisIndexOid = lfirst_oid(index);
+               bool            dirty = false;
+
+               pg_index_tuple = SearchSysCacheCopy1(INDEXRELID,
+                                                                                ObjectIdGetDatum(thisIndexOid));
+               if (!HeapTupleIsValid(pg_index_tuple))
+                       elog(ERROR, "cache lookup failed for index %u", thisIndexOid);
+               pg_index_form = (Form_pg_index) GETSTRUCT(pg_index_tuple);
+
+               /*
+                * Unset the bit if set.  We know it's wrong because we checked this
+                * earlier.
+                */
+               if (pg_index_form->indisreplident)
+               {
+                       dirty = true;
+                       pg_index_form->indisreplident = false;
+               }
+               else if (thisIndexOid == indexOid)
+               {
+                       dirty = true;
+                       pg_index_form->indisreplident = true;
+               }
+
+               if (dirty)
+               {
+                       simple_heap_update(pg_index, &pg_index_tuple->t_self, pg_index_tuple);
+                       CatalogUpdateIndexes(pg_index, pg_index_tuple);
+                       InvokeObjectPostAlterHookArg(IndexRelationId, thisIndexOid, 0,
+                                                    InvalidOid, is_internal);
+               }
+               heap_freetuple(pg_index_tuple);
+       }
+
+       heap_close(pg_index, RowExclusiveLock);
+}
+
+/*
+ * ALTER TABLE <name> REPLICA IDENTITY ...
+ */
+static void
+ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode)
+{
+       Oid                     indexOid;
+       Relation        indexRel;
+       int                     key;
+
+       if (stmt->identity_type == REPLICA_IDENTITY_DEFAULT)
+       {
+               relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
+               return;
+       }
+       else if (stmt->identity_type == REPLICA_IDENTITY_FULL)
+       {
+               relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
+               return;
+       }
+       else if (stmt->identity_type == REPLICA_IDENTITY_NOTHING)
+       {
+               relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
+               return;
+       }
+       else if (stmt->identity_type == REPLICA_IDENTITY_INDEX)
+       {
+               /* fallthrough */;
+       }
+       else
+               elog(ERROR, "unexpected identity type %u", stmt->identity_type);
+
+
+       /* Check that the index exists */
+       indexOid = get_relname_relid(stmt->name, rel->rd_rel->relnamespace);
+       if (!OidIsValid(indexOid))
+               ereport(ERROR,
+                               (errcode(ERRCODE_UNDEFINED_OBJECT),
+                                errmsg("index \"%s\" for table \"%s\" does not exist",
+                                               stmt->name, RelationGetRelationName(rel))));
+
+       indexRel = index_open(indexOid, ShareLock);
+
+       /* Check that the index is on the relation we're altering. */
+       if (indexRel->rd_index == NULL ||
+               indexRel->rd_index->indrelid != RelationGetRelid(rel))
+               ereport(ERROR,
+                               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                                errmsg("\"%s\" is not an index for table \"%s\"",
+                                               RelationGetRelationName(indexRel),
+                                               RelationGetRelationName(rel))));
+       /* The AM must support uniqueness, and the index must in fact be unique. */
+       if (!indexRel->rd_am->amcanunique || !indexRel->rd_index->indisunique)
+               ereport(ERROR,
+                               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                                errmsg("cannot use non-unique index \"%s\" as replica identity",
+                                               RelationGetRelationName(indexRel))));
+       /* Deferred indexes are not guaranteed to be always unique. */
+       if (!indexRel->rd_index->indimmediate)
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("cannot use non-immediate index \"%s\" as replica identity",
+                                               RelationGetRelationName(indexRel))));
+       /* Expression indexes aren't supported. */
+       if (RelationGetIndexExpressions(indexRel) != NIL)
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("cannot use expression index \"%s\" as replica identity",
+                                               RelationGetRelationName(indexRel))));
+       /* Predicate indexes aren't supported. */
+       if (RelationGetIndexPredicate(indexRel) != NIL)
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("cannot use partial index \"%s\" as replica identity",
+                                               RelationGetRelationName(indexRel))));
+       /* And neither are invalid indexes. */
+       if (!IndexIsValid(indexRel->rd_index))
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("cannot use invalid index \"%s\" as replica identity",
+                                               RelationGetRelationName(indexRel))));
+
+       /* Check index for nullable columns. */
+       for (key = 0; key < indexRel->rd_index->indnatts; key++)
+       {
+               int16 attno = indexRel->rd_index->indkey.values[key];
+               Form_pg_attribute attr;
+
+               /* Of the system columns, only oid is indexable. */
+               if (attno <= 0 && attno != ObjectIdAttributeNumber)
+                       elog(ERROR, "internal column %u in unique index \"%s\"",
+                            attno, RelationGetRelationName(indexRel));
+
+               attr = rel->rd_att->attrs[attno - 1];
+               if (!attr->attnotnull)
+                       ereport(ERROR,
+                               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                                errmsg("index \"%s\" cannot be used as replica identity because column \"%s\" is nullable",
+                                       RelationGetRelationName(indexRel),
+                                       NameStr(attr->attname))));
+       }
+
+       /* This index is suitable for use as a replica identity. Mark it. */
+       relation_mark_replica_identity(rel, stmt->identity_type, indexOid, true);
+
+       index_close(indexRel, NoLock);
+}
+
 /*
  * ALTER FOREIGN TABLE <name> OPTIONS (...)
  */
index 65f3b984d647a5ff308b66ae4d3d8403f0a045f5..1733da633a316247be2fb891bc66cab42fce27b4 100644 (file)
@@ -3270,6 +3270,17 @@ _copyRefreshMatViewStmt(const RefreshMatViewStmt *from)
        return newnode;
 }
 
+static ReplicaIdentityStmt *
+_copyReplicaIdentityStmt(const ReplicaIdentityStmt *from)
+{
+       ReplicaIdentityStmt *newnode = makeNode(ReplicaIdentityStmt);
+
+       COPY_SCALAR_FIELD(identity_type);
+       COPY_STRING_FIELD(name);
+
+       return newnode;
+}
+
 static CreateSeqStmt *
 _copyCreateSeqStmt(const CreateSeqStmt *from)
 {
@@ -4343,6 +4354,9 @@ copyObject(const void *from)
                case T_RefreshMatViewStmt:
                        retval = _copyRefreshMatViewStmt(from);
                        break;
+               case T_ReplicaIdentityStmt:
+                       retval = _copyReplicaIdentityStmt(from);
+                       break;
                case T_CreateSeqStmt:
                        retval = _copyCreateSeqStmt(from);
                        break;
index 4c9b05e1e4ddfe29036c9597f99a47bd940fcf22..7b29812b696dcb588fd9ee676e2db056fed697f0 100644 (file)
@@ -1537,6 +1537,15 @@ _equalRefreshMatViewStmt(const RefreshMatViewStmt *a, const RefreshMatViewStmt *
        return true;
 }
 
+static bool
+_equalReplicaIdentityStmt(const ReplicaIdentityStmt *a, const ReplicaIdentityStmt *b)
+{
+       COMPARE_SCALAR_FIELD(identity_type);
+       COMPARE_STRING_FIELD(name);
+
+       return true;
+}
+
 static bool
 _equalCreateSeqStmt(const CreateSeqStmt *a, const CreateSeqStmt *b)
 {
@@ -2813,6 +2822,9 @@ equal(const void *a, const void *b)
                case T_RefreshMatViewStmt:
                        retval = _equalRefreshMatViewStmt(a, b);
                        break;
+               case T_ReplicaIdentityStmt:
+                       retval = _equalReplicaIdentityStmt(a, b);
+                       break;
                case T_CreateSeqStmt:
                        retval = _equalCreateSeqStmt(a, b);
                        break;
index 363c603848db457e4510a75b6157346d00607dc0..8dc4b1cc53bac1cbbebc38a1775207560331489e 100644 (file)
@@ -255,6 +255,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <ival>   add_drop opt_asc_desc opt_nulls_order
 
 %type <node>   alter_table_cmd alter_type_cmd opt_collate_clause
+          replica_identity
 %type <list>   alter_table_cmds alter_type_cmds
 
 %type <dbehavior>      opt_drop_behavior
@@ -2178,6 +2179,14 @@ alter_table_cmd:
                                        n->def = (Node *)$2;
                                        $$ = (Node *)n;
                                }
+                       /* ALTER TABLE <name> REPLICA IDENTITY  */
+                       | REPLICA IDENTITY_P replica_identity
+                               {
+                                       AlterTableCmd *n = makeNode(AlterTableCmd);
+                                       n->subtype = AT_ReplicaIdentity;
+                                       n->def = $3;
+                                       $$ = (Node *)n;
+                               }
                        | alter_generic_options
                                {
                                        AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -2215,6 +2224,37 @@ alter_using:
                        | /* EMPTY */                           { $$ = NULL; }
                ;
 
+replica_identity:
+                       NOTHING
+                               {
+                                       ReplicaIdentityStmt *n = makeNode(ReplicaIdentityStmt);
+                                       n->identity_type = REPLICA_IDENTITY_NOTHING;
+                                       n->name = NULL;
+                                       $$ = (Node *) n;
+                               }
+                       | FULL
+                               {
+                                       ReplicaIdentityStmt *n = makeNode(ReplicaIdentityStmt);
+                                       n->identity_type = REPLICA_IDENTITY_FULL;
+                                       n->name = NULL;
+                                       $$ = (Node *) n;
+                               }
+                       | DEFAULT
+                               {
+                                       ReplicaIdentityStmt *n = makeNode(ReplicaIdentityStmt);
+                                       n->identity_type = REPLICA_IDENTITY_DEFAULT;
+                                       n->name = NULL;
+                                       $$ = (Node *) n;
+                               }
+                       | USING INDEX name
+                               {
+                                       ReplicaIdentityStmt *n = makeNode(ReplicaIdentityStmt);
+                                       n->identity_type = REPLICA_IDENTITY_INDEX;
+                                       n->name = $3;
+                                       $$ = (Node *) n;
+                               }
+;
+
 reloptions:
                        '(' reloption_list ')'                                  { $$ = $2; }
                ;
index b4cc6ad221063867c4a5fc1cb781d6773f2e10aa..9d8caffcacc5af14b2d8285d990b674d2b646c36 100644 (file)
@@ -1454,6 +1454,7 @@ formrdesc(const char *relationName, Oid relationReltype,
        /* ... and they're always populated, too */
        relation->rd_rel->relispopulated = true;
 
+       relation->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
        relation->rd_rel->relpages = 0;
        relation->rd_rel->reltuples = 0;
        relation->rd_rel->relallvisible = 0;
@@ -2664,6 +2665,13 @@ RelationBuildLocalRelation(const char *relname,
        else
                rel->rd_rel->relispopulated = true;
 
+       /* system relations and non-table objects don't have one */
+       if (!IsSystemNamespace(relnamespace) &&
+           (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+               rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
+       else
+               rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
+
        /*
         * Insert relation physical and logical identifiers (OIDs) into the right
         * places.      For a mapped relation, we set relfilenode to zero and rely on
@@ -3462,7 +3470,10 @@ RelationGetIndexList(Relation relation)
        ScanKeyData skey;
        HeapTuple       htup;
        List       *result;
-       Oid                     oidIndex;
+       char            replident = relation->rd_rel->relreplident;
+       Oid                     oidIndex = InvalidOid;
+       Oid                     pkeyIndex = InvalidOid;
+       Oid                     candidateIndex = InvalidOid;
        MemoryContext oldcxt;
 
        /* Quick exit if we already computed the list. */
@@ -3519,17 +3530,45 @@ RelationGetIndexList(Relation relation)
                Assert(!isnull);
                indclass = (oidvector *) DatumGetPointer(indclassDatum);
 
-               /* Check to see if it is a unique, non-partial btree index on OID */
-               if (IndexIsValid(index) &&
-                       index->indnatts == 1 &&
-                       index->indisunique && index->indimmediate &&
+               /*
+                * Invalid, non-unique, non-immediate or predicate indexes aren't
+                * interesting for neither oid indexes nor replication identity
+                * indexes, so don't check them.
+                */
+               if (!IndexIsValid(index) || !index->indisunique ||
+                       !index->indimmediate ||
+                       !heap_attisnull(htup, Anum_pg_index_indpred))
+                       continue;
+
+               /* Check to see if is a usable btree index on OID */
+               if (index->indnatts == 1 &&
                        index->indkey.values[0] == ObjectIdAttributeNumber &&
-                       indclass->values[0] == OID_BTREE_OPS_OID &&
-                       heap_attisnull(htup, Anum_pg_index_indpred))
+                       indclass->values[0] == OID_BTREE_OPS_OID)
                        oidIndex = index->indexrelid;
+
+               /* always prefer primary keys */
+               if (index->indisprimary)
+                       pkeyIndex = index->indexrelid;
+
+               /* explicitly chosen index */
+               if (index->indisreplident)
+                       candidateIndex = index->indexrelid;
        }
 
        systable_endscan(indscan);
+
+       /* primary key */
+       if (replident == REPLICA_IDENTITY_DEFAULT &&
+               OidIsValid(pkeyIndex))
+               relation->rd_replidindex = pkeyIndex;
+       /* explicitly chosen index */
+       else if (replident == REPLICA_IDENTITY_INDEX &&
+                        OidIsValid(candidateIndex))
+               relation->rd_replidindex = candidateIndex;
+       /* nothing */
+       else
+               relation->rd_replidindex = InvalidOid;
+
        heap_close(indrel, AccessShareLock);
 
        /* Now save a copy of the completed list in the relcache entry. */
index bc70dd6a6ed2f8e38b728228b373ccb559fdd03e..9478aacf8395322f8763bc891a0d6b38f7e86594 100644 (file)
@@ -4221,6 +4221,7 @@ getTables(Archive *fout, int *numTables)
        int                     i_toastfrozenxid;
        int                     i_relpersistence;
        int                     i_relispopulated;
+       int                     i_relreplident;
        int                     i_owning_tab;
        int                     i_owning_col;
        int                     i_reltablespace;
@@ -4253,7 +4254,7 @@ getTables(Archive *fout, int *numTables)
         * we cannot correctly identify inherited columns, owned sequences, etc.
         */
 
-       if (fout->remoteVersion >= 90300)
+       if (fout->remoteVersion >= 90400)
        {
                /*
                 * Left join to pick up dependency info linking sequences to their
@@ -4268,7 +4269,46 @@ getTables(Archive *fout, int *numTables)
                                                  "c.relfrozenxid, tc.oid AS toid, "
                                                  "tc.relfrozenxid AS tfrozenxid, "
                                                  "c.relpersistence, c.relispopulated, "
-                                                 "c.relpages, "
+                                                 "c.relreplident, c.relpages, "
+                                                 "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
+                                                 "d.refobjid AS owning_tab, "
+                                                 "d.refobjsubid AS owning_col, "
+                                                 "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
+                                               "array_to_string(array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded'), ', ') AS reloptions, "
+                                                 "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text "
+                                                          "WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, "
+                                                 "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+                                                 "FROM pg_class c "
+                                                 "LEFT JOIN pg_depend d ON "
+                                                 "(c.relkind = '%c' AND "
+                                                 "d.classid = c.tableoid AND d.objid = c.oid AND "
+                                                 "d.objsubid = 0 AND "
+                                                 "d.refclassid = c.tableoid AND d.deptype = 'a') "
+                                          "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) "
+                                  "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+                                                 "ORDER BY c.oid",
+                                                 username_subquery,
+                                                 RELKIND_SEQUENCE,
+                                                 RELKIND_RELATION, RELKIND_SEQUENCE,
+                                                 RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
+                                                 RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+       }
+       else if (fout->remoteVersion >= 90300)
+       {
+               /*
+                * Left join to pick up dependency info linking sequences to their
+                * owning column, if any (note this dependency is AUTO as of 8.2)
+                */
+               appendPQExpBuffer(query,
+                                                 "SELECT c.tableoid, c.oid, c.relname, "
+                                                 "c.relacl, c.relkind, c.relnamespace, "
+                                                 "(%s c.relowner) AS rolname, "
+                                                 "c.relchecks, c.relhastriggers, "
+                                                 "c.relhasindex, c.relhasrules, c.relhasoids, "
+                                                 "c.relfrozenxid, tc.oid AS toid, "
+                                                 "tc.relfrozenxid AS tfrozenxid, "
+                                                 "c.relpersistence, c.relispopulated, "
+                                                 "'d' AS relreplident, c.relpages, "
                                                  "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
                                                  "d.refobjid AS owning_tab, "
                                                  "d.refobjsubid AS owning_col, "
@@ -4307,7 +4347,7 @@ getTables(Archive *fout, int *numTables)
                                                  "c.relfrozenxid, tc.oid AS toid, "
                                                  "tc.relfrozenxid AS tfrozenxid, "
                                                  "c.relpersistence, 't' as relispopulated, "
-                                                 "c.relpages, "
+                                                 "'d' AS relreplident, c.relpages, "
                                                  "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
                                                  "d.refobjid AS owning_tab, "
                                                  "d.refobjsubid AS owning_col, "
@@ -4344,7 +4384,7 @@ getTables(Archive *fout, int *numTables)
                                                  "c.relfrozenxid, tc.oid AS toid, "
                                                  "tc.relfrozenxid AS tfrozenxid, "
                                                  "'p' AS relpersistence, 't' as relispopulated, "
-                                                 "c.relpages, "
+                                                 "'d' AS relreplident, c.relpages, "
                                                  "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
                                                  "d.refobjid AS owning_tab, "
                                                  "d.refobjsubid AS owning_col, "
@@ -4380,7 +4420,7 @@ getTables(Archive *fout, int *numTables)
                                                  "c.relfrozenxid, tc.oid AS toid, "
                                                  "tc.relfrozenxid AS tfrozenxid, "
                                                  "'p' AS relpersistence, 't' as relispopulated, "
-                                                 "c.relpages, "
+                                                 "'d' AS relreplident, c.relpages, "
                                                  "NULL AS reloftype, "
                                                  "d.refobjid AS owning_tab, "
                                                  "d.refobjsubid AS owning_col, "
@@ -4416,7 +4456,7 @@ getTables(Archive *fout, int *numTables)
                                                  "c.relfrozenxid, tc.oid AS toid, "
                                                  "tc.relfrozenxid AS tfrozenxid, "
                                                  "'p' AS relpersistence, 't' as relispopulated, "
-                                                 "c.relpages, "
+                                                 "'d' AS relreplident, c.relpages, "
                                                  "NULL AS reloftype, "
                                                  "d.refobjid AS owning_tab, "
                                                  "d.refobjsubid AS owning_col, "
@@ -4453,7 +4493,7 @@ getTables(Archive *fout, int *numTables)
                                                  "0 AS toid, "
                                                  "0 AS tfrozenxid, "
                                                  "'p' AS relpersistence, 't' as relispopulated, "
-                                                 "relpages, "
+                                                 "'d' AS relreplident, relpages, "
                                                  "NULL AS reloftype, "
                                                  "d.refobjid AS owning_tab, "
                                                  "d.refobjsubid AS owning_col, "
@@ -4489,7 +4529,7 @@ getTables(Archive *fout, int *numTables)
                                                  "0 AS toid, "
                                                  "0 AS tfrozenxid, "
                                                  "'p' AS relpersistence, 't' as relispopulated, "
-                                                 "relpages, "
+                                                 "'d' AS relreplident, relpages, "
                                                  "NULL AS reloftype, "
                                                  "d.refobjid AS owning_tab, "
                                                  "d.refobjsubid AS owning_col, "
@@ -4521,7 +4561,7 @@ getTables(Archive *fout, int *numTables)
                                                  "0 AS toid, "
                                                  "0 AS tfrozenxid, "
                                                  "'p' AS relpersistence, 't' as relispopulated, "
-                                                 "relpages, "
+                                                 "'d' AS relreplident, relpages, "
                                                  "NULL AS reloftype, "
                                                  "NULL::oid AS owning_tab, "
                                                  "NULL::int4 AS owning_col, "
@@ -4548,7 +4588,7 @@ getTables(Archive *fout, int *numTables)
                                                  "0 AS toid, "
                                                  "0 AS tfrozenxid, "
                                                  "'p' AS relpersistence, 't' as relispopulated, "
-                                                 "relpages, "
+                                                 "'d' AS relreplident, relpages, "
                                                  "NULL AS reloftype, "
                                                  "NULL::oid AS owning_tab, "
                                                  "NULL::int4 AS owning_col, "
@@ -4585,7 +4625,7 @@ getTables(Archive *fout, int *numTables)
                                                  "0 AS toid, "
                                                  "0 AS tfrozenxid, "
                                                  "'p' AS relpersistence, 't' as relispopulated, "
-                                                 "0 AS relpages, "
+                                                 "'d' AS relreplident, 0 AS relpages, "
                                                  "NULL AS reloftype, "
                                                  "NULL::oid AS owning_tab, "
                                                  "NULL::int4 AS owning_col, "
@@ -4634,6 +4674,7 @@ getTables(Archive *fout, int *numTables)
        i_toastfrozenxid = PQfnumber(res, "tfrozenxid");
        i_relpersistence = PQfnumber(res, "relpersistence");
        i_relispopulated = PQfnumber(res, "relispopulated");
+       i_relreplident = PQfnumber(res, "relreplident");
        i_relpages = PQfnumber(res, "relpages");
        i_owning_tab = PQfnumber(res, "owning_tab");
        i_owning_col = PQfnumber(res, "owning_col");
@@ -4678,6 +4719,7 @@ getTables(Archive *fout, int *numTables)
                tblinfo[i].hastriggers = (strcmp(PQgetvalue(res, i, i_relhastriggers), "t") == 0);
                tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
                tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
+               tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident));
                tblinfo[i].relpages = atoi(PQgetvalue(res, i, i_relpages));
                tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid));
                tblinfo[i].toast_oid = atooid(PQgetvalue(res, i, i_toastoid));
@@ -4863,6 +4905,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
                                i_indnkeys,
                                i_indkey,
                                i_indisclustered,
+                               i_indisreplident,
                                i_contype,
                                i_conname,
                                i_condeferrable,
@@ -4909,7 +4952,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
                 * is not.
                 */
                resetPQExpBuffer(query);
-               if (fout->remoteVersion >= 90000)
+               if (fout->remoteVersion >= 90400)
                {
                        /*
                         * the test on indisready is necessary in 9.2, and harmless in
@@ -4921,7 +4964,38 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
                                         "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
                                                          "t.relnatts AS indnkeys, "
                                                          "i.indkey, i.indisclustered, "
-                                                         "t.relpages, "
+                                                         "i.indisreplident, t.relpages, "
+                                                         "c.contype, c.conname, "
+                                                         "c.condeferrable, c.condeferred, "
+                                                         "c.tableoid AS contableoid, "
+                                                         "c.oid AS conoid, "
+                                 "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
+                                                         "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
+                                                       "array_to_string(t.reloptions, ', ') AS options "
+                                                         "FROM pg_catalog.pg_index i "
+                                         "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
+                                                         "LEFT JOIN pg_catalog.pg_constraint c "
+                                                         "ON (i.indrelid = c.conrelid AND "
+                                                         "i.indexrelid = c.conindid AND "
+                                                         "c.contype IN ('p','u','x')) "
+                                                         "WHERE i.indrelid = '%u'::pg_catalog.oid "
+                                                         "AND i.indisvalid AND i.indisready "
+                                                         "ORDER BY indexname",
+                                                         tbinfo->dobj.catId.oid);
+               }
+               else if (fout->remoteVersion >= 90000)
+               {
+                       /*
+                        * the test on indisready is necessary in 9.2, and harmless in
+                        * earlier/later versions
+                        */
+                       appendPQExpBuffer(query,
+                                                         "SELECT t.tableoid, t.oid, "
+                                                         "t.relname AS indexname, "
+                                        "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
+                                                         "t.relnatts AS indnkeys, "
+                                                         "i.indkey, i.indisclustered, "
+                                                         "false AS indisreplident, t.relpages, "
                                                          "c.contype, c.conname, "
                                                          "c.condeferrable, c.condeferred, "
                                                          "c.tableoid AS contableoid, "
@@ -4948,7 +5022,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
                                         "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
                                                          "t.relnatts AS indnkeys, "
                                                          "i.indkey, i.indisclustered, "
-                                                         "t.relpages, "
+                                                         "false AS indisreplident, t.relpages, "
                                                          "c.contype, c.conname, "
                                                          "c.condeferrable, c.condeferred, "
                                                          "c.tableoid AS contableoid, "
@@ -4978,7 +5052,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
                                         "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
                                                          "t.relnatts AS indnkeys, "
                                                          "i.indkey, i.indisclustered, "
-                                                         "t.relpages, "
+                                                         "false AS indisreplident, t.relpages, "
                                                          "c.contype, c.conname, "
                                                          "c.condeferrable, c.condeferred, "
                                                          "c.tableoid AS contableoid, "
@@ -5007,7 +5081,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
                                         "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
                                                          "t.relnatts AS indnkeys, "
                                                          "i.indkey, i.indisclustered, "
-                                                         "t.relpages, "
+                                                         "false AS indisreplident, t.relpages, "
                                                          "c.contype, c.conname, "
                                                          "c.condeferrable, c.condeferred, "
                                                          "c.tableoid AS contableoid, "
@@ -5036,7 +5110,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
                                                          "pg_get_indexdef(i.indexrelid) AS indexdef, "
                                                          "t.relnatts AS indnkeys, "
                                                          "i.indkey, false AS indisclustered, "
-                                                         "t.relpages, "
+                                                         "false AS indisreplident, t.relpages, "
                                                          "CASE WHEN i.indisprimary THEN 'p'::char "
                                                          "ELSE '0'::char END AS contype, "
                                                          "t.relname AS conname, "
@@ -5063,7 +5137,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
                                                          "pg_get_indexdef(i.indexrelid) AS indexdef, "
                                                          "t.relnatts AS indnkeys, "
                                                          "i.indkey, false AS indisclustered, "
-                                                         "t.relpages, "
+                                                         "false AS indisreplident, t.relpages, "
                                                          "CASE WHEN i.indisprimary THEN 'p'::char "
                                                          "ELSE '0'::char END AS contype, "
                                                          "t.relname AS conname, "
@@ -5092,6 +5166,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
                i_indnkeys = PQfnumber(res, "indnkeys");
                i_indkey = PQfnumber(res, "indkey");
                i_indisclustered = PQfnumber(res, "indisclustered");
+               i_indisreplident = PQfnumber(res, "indisreplident");
                i_relpages = PQfnumber(res, "relpages");
                i_contype = PQfnumber(res, "contype");
                i_conname = PQfnumber(res, "conname");
@@ -5135,6 +5210,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
                        parseOidArray(PQgetvalue(res, j, i_indkey),
                                                  indxinfo[j].indkeys, INDEX_MAX_KEYS);
                        indxinfo[j].indisclustered = (PQgetvalue(res, j, i_indisclustered)[0] == 't');
+                       indxinfo[j].indisreplident = (PQgetvalue(res, j, i_indisreplident)[0] == 't');
                        indxinfo[j].relpages = atoi(PQgetvalue(res, j, i_relpages));
                        contype = *(PQgetvalue(res, j, i_contype));
 
@@ -13408,6 +13484,28 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
                }
        }
 
+       /*
+        * dump properties we only have ALTER TABLE syntax for
+        */
+       if ((tbinfo->relkind == RELKIND_RELATION || tbinfo->relkind == RELKIND_MATVIEW) &&
+           tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
+       {
+               if (tbinfo->relreplident == REPLICA_IDENTITY_INDEX)
+               {
+                       /* nothing to do, will be set when the index is dumped */
+               }
+               else if (tbinfo->relreplident == REPLICA_IDENTITY_NOTHING)
+               {
+                       appendPQExpBuffer(q, "\nALTER TABLE ONLY %s REPLICA IDENTITY NOTHING;\n",
+                                         fmtId(tbinfo->dobj.name));
+               }
+               else if (tbinfo->relreplident == REPLICA_IDENTITY_FULL)
+               {
+                       appendPQExpBuffer(q, "\nALTER TABLE ONLY %s REPLICA IDENTITY FULL;\n",
+                                         fmtId(tbinfo->dobj.name));
+               }
+       }
+
        if (binary_upgrade)
                binary_upgrade_extension_member(q, &tbinfo->dobj, labelq->data);
 
@@ -13579,6 +13677,15 @@ dumpIndex(Archive *fout, IndxInfo *indxinfo)
                                                          fmtId(indxinfo->dobj.name));
                }
 
+               /* If the index is clustered, we need to record that. */
+               if (indxinfo->indisreplident)
+               {
+                       appendPQExpBuffer(q, "\nALTER TABLE ONLY %s REPLICA IDENTITY USING",
+                                         fmtId(tbinfo->dobj.name));
+                       appendPQExpBuffer(q, " INDEX %s;\n",
+                                         fmtId(indxinfo->dobj.name));
+               }
+
                /*
                 * DROP must be fully qualified in case same name appears in
                 * pg_catalog
index 2c5971c516a14a6a9d6f44d6768fc147c9e629f9..915e82c64f6e0efff6e872ef56e71063cf9f16a7 100644 (file)
@@ -237,6 +237,7 @@ typedef struct _tableInfo
        char            relkind;
        char            relpersistence; /* relation persistence */
        bool            relispopulated; /* relation is populated */
+       bool            relreplident;   /* replica identifier */
        char       *reltablespace;      /* relation tablespace */
        char       *reloptions;         /* options specified by WITH (...) */
        char       *checkoption;        /* WITH CHECK OPTION */
@@ -315,6 +316,7 @@ typedef struct _indxInfo
        int                     indnkeys;
        Oid                *indkeys;
        bool            indisclustered;
+       bool            indisreplident;
        /* if there is an associated constraint object, its dumpId: */
        DumpId          indexconstraint;
        int                     relpages;               /* relpages of the underlying table */
index ed1c5fdabc81b516ff79ed8e93c3e44cbaecbf06..76953f21a09f81440a54278d896885c7334ac617 100644 (file)
@@ -1156,6 +1156,7 @@ describeOneTableDetails(const char *schemaname,
                char       *reloptions;
                char       *reloftype;
                char            relpersistence;
+               char            relreplident;
        }                       tableinfo;
        bool            show_modifiers = false;
        bool            retval;
@@ -1171,7 +1172,24 @@ describeOneTableDetails(const char *schemaname,
        initPQExpBuffer(&tmpbuf);
 
        /* Get general table info */
-       if (pset.sversion >= 90100)
+       if (pset.sversion >= 90400)
+       {
+               printfPQExpBuffer(&buf,
+                         "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
+                                                 "c.relhastriggers, c.relhasoids, "
+                                                 "%s, c.reltablespace, "
+                                                 "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
+                                                 "c.relpersistence, c.relreplident\n"
+                                                 "FROM pg_catalog.pg_class c\n "
+                  "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
+                                                 "WHERE c.oid = '%s';",
+                                                 (verbose ?
+                                                  "pg_catalog.array_to_string(c.reloptions || "
+                                                  "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
+                                                  : "''"),
+                                                 oid);
+       }
+       else if (pset.sversion >= 90100)
        {
                printfPQExpBuffer(&buf,
                          "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
@@ -1276,6 +1294,8 @@ describeOneTableDetails(const char *schemaname,
                pg_strdup(PQgetvalue(res, 0, 8)) : NULL;
        tableinfo.relpersistence = (pset.sversion >= 90100) ?
                *(PQgetvalue(res, 0, 9)) : 0;
+       tableinfo.relreplident = (pset.sversion >= 90400) ?
+               *(PQgetvalue(res, 0, 10)) : 'd';
        PQclear(res);
        res = NULL;
 
@@ -1589,6 +1609,12 @@ describeOneTableDetails(const char *schemaname,
                else
                        appendPQExpBuffer(&buf,
                                                "  false AS condeferrable, false AS condeferred,\n");
+
+               if (pset.sversion >= 90400)
+                       appendPQExpBuffer(&buf, "i.indisidentity,\n");
+               else
+                       appendPQExpBuffer(&buf, "false AS indisidentity,\n");
+
                appendPQExpBuffer(&buf, "  a.amname, c2.relname, "
                                          "pg_catalog.pg_get_expr(i.indpred, i.indrelid, true)\n"
                                                  "FROM pg_catalog.pg_index i, pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_am a\n"
@@ -1612,9 +1638,10 @@ describeOneTableDetails(const char *schemaname,
                        char       *indisvalid = PQgetvalue(result, 0, 3);
                        char       *deferrable = PQgetvalue(result, 0, 4);
                        char       *deferred = PQgetvalue(result, 0, 5);
-                       char       *indamname = PQgetvalue(result, 0, 6);
-                       char       *indtable = PQgetvalue(result, 0, 7);
-                       char       *indpred = PQgetvalue(result, 0, 8);
+                       char       *indisidentity = PQgetvalue(result, 0, 6);
+                       char       *indamname = PQgetvalue(result, 0, 7);
+                       char       *indtable = PQgetvalue(result, 0, 8);
+                       char       *indpred = PQgetvalue(result, 0, 9);
 
                        if (strcmp(indisprimary, "t") == 0)
                                printfPQExpBuffer(&tmpbuf, _("primary key, "));
@@ -1643,6 +1670,9 @@ describeOneTableDetails(const char *schemaname,
                        if (strcmp(deferred, "t") == 0)
                                appendPQExpBuffer(&tmpbuf, _(", initially deferred"));
 
+                       if (strcmp(indisidentity, "t") == 0)
+                               appendPQExpBuffer(&tmpbuf, _(", replica identity"));
+
                        printTableAddFooter(&cont, tmpbuf.data);
                        add_tablespace_footer(&cont, tableinfo.relkind,
                                                                  tableinfo.tablespace, true);
@@ -1713,6 +1743,10 @@ describeOneTableDetails(const char *schemaname,
                                appendPQExpBuffer(&buf,
                                                                  "null AS constraintdef, null AS contype, "
                                                         "false AS condeferrable, false AS condeferred");
+                       if (pset.sversion >= 90400)
+                               appendPQExpBuffer(&buf, ", i.indisreplident");
+                       else
+                               appendPQExpBuffer(&buf, ", false AS indisreplident");
                        if (pset.sversion >= 80000)
                                appendPQExpBuffer(&buf, ", c2.reltablespace");
                        appendPQExpBuffer(&buf,
@@ -1783,12 +1817,15 @@ describeOneTableDetails(const char *schemaname,
                                        if (strcmp(PQgetvalue(result, i, 4), "t") != 0)
                                                appendPQExpBuffer(&buf, " INVALID");
 
+                                       if (strcmp(PQgetvalue(result, i, 10), "t") == 0)
+                                               appendPQExpBuffer(&buf, " REPLICA IDENTITY");
+
                                        printTableAddFooter(&cont, buf.data);
 
                                        /* Print tablespace of the index on the same line */
                                        if (pset.sversion >= 80000)
                                                add_tablespace_footer(&cont, 'i',
-                                                                                  atooid(PQgetvalue(result, i, 10)),
+                                                                                  atooid(PQgetvalue(result, i, 11)),
                                                                                          false);
                                }
                        }
@@ -2273,6 +2310,17 @@ describeOneTableDetails(const char *schemaname,
                        printTableAddFooter(&cont, buf.data);
                }
 
+               if ((tableinfo.relkind == 'r' || tableinfo.relkind == 'm') &&
+                   tableinfo.relreplident != 'd' && tableinfo.relreplident != 'i')
+               {
+                       const char *s = _("Replica Identity");
+
+                       printfPQExpBuffer(&buf, "%s: %s",
+                                         s,
+                                         tableinfo.relreplident == 'n' ? "NOTHING" : "FULL");
+                       printTableAddFooter(&cont, buf.data);
+               }
+
                /* OIDs, if verbose and not a materialized view */
                if (verbose && tableinfo.relkind != 'm')
                {
index 021b6c5a00d9ef9cacaab77c558f8a8511686b79..5287d1c7c6c07bf741f67fa30c3a8158025180e7 100644 (file)
@@ -1336,7 +1336,7 @@ psql_completion(char *text, int start, int end)
                static const char *const list_ALTER2[] =
                {"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT",
                        "NO INHERIT", "RENAME", "RESET", "OWNER TO", "SET",
-               "VALIDATE CONSTRAINT", NULL};
+                "VALIDATE CONSTRAINT", "REPLICA IDENTITY", NULL};
 
                COMPLETE_WITH_LIST(list_ALTER2);
        }
@@ -1581,6 +1581,35 @@ psql_completion(char *text, int start, int end)
 
                COMPLETE_WITH_LIST(list_TABLEOPTIONS);
        }
+       else if (pg_strcasecmp(prev4_wd, "REPLICA") == 0 &&
+                        pg_strcasecmp(prev3_wd, "IDENTITY") == 0 &&
+                        pg_strcasecmp(prev2_wd, "USING") == 0 &&
+                pg_strcasecmp(prev_wd, "INDEX") == 0)
+       {
+               completion_info_charp = prev5_wd;
+               COMPLETE_WITH_QUERY(Query_for_index_of_table);
+       }
+       else if (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
+                        pg_strcasecmp(prev3_wd, "REPLICA") == 0 &&
+                        pg_strcasecmp(prev2_wd, "IDENTITY") == 0 &&
+                        pg_strcasecmp(prev_wd, "USING") == 0)
+       {
+               COMPLETE_WITH_CONST("INDEX");
+       }
+       else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
+                        pg_strcasecmp(prev2_wd, "REPLICA") == 0 &&
+                        pg_strcasecmp(prev_wd, "IDENTITY") == 0)
+       {
+               static const char *const list_REPLICAID[] =
+               {"FULL", "NOTHING", "DEFAULT", "USING", NULL};
+
+               COMPLETE_WITH_LIST(list_REPLICAID);
+       }
+       else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
+                        pg_strcasecmp(prev_wd, "REPLICA") == 0)
+       {
+               COMPLETE_WITH_CONST("IDENTITY");
+       }
 
        /* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
index 4aa0edf3999dd8723d8aa1dea7ecb1442e34dd3d..4108f6c16c046af629cad1c525a8b75827b7ef6e 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201310271
+#define CATALOG_VERSION_NO     201311081
 
 #endif
index 49c4f6f136bd26fa4fab2c5838ac8c137907ddd6..a1fee11fe6f62326349b20291ad803793dedea3c 100644 (file)
@@ -66,6 +66,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
        bool            relhastriggers; /* has (or has had) any TRIGGERs */
        bool            relhassubclass; /* has (or has had) derived classes */
        bool            relispopulated; /* matview currently holds query results */
+       char            relreplident;   /* see REPLICA_IDENTITY_xxx constants  */
        TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
        TransactionId relminmxid;       /* all multixacts in this rel are >= this.
                                                                 * this is really a MultiXactId */
@@ -93,7 +94,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class                                 28
+#define Natts_pg_class                                 29
 #define Anum_pg_class_relname                  1
 #define Anum_pg_class_relnamespace             2
 #define Anum_pg_class_reltype                  3
@@ -118,10 +119,11 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relhastriggers   22
 #define Anum_pg_class_relhassubclass   23
 #define Anum_pg_class_relispopulated   24
-#define Anum_pg_class_relfrozenxid             25
-#define Anum_pg_class_relminmxid               26
-#define Anum_pg_class_relacl                   27
-#define Anum_pg_class_reloptions               28
+#define Anum_pg_class_relreplident             25
+#define Anum_pg_class_relfrozenxid             26
+#define Anum_pg_class_relminmxid               27
+#define Anum_pg_class_relacl                   28
+#define Anum_pg_class_reloptions               29
 
 /* ----------------
  *             initial contents of pg_class
@@ -136,13 +138,13 @@ typedef FormData_pg_class *Form_pg_class;
  * Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
  * similarly, "1" in relminmxid stands for FirstMultiXactId
  */
-DATA(insert OID = 1247 (  pg_type              PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f t 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type              PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f t 3 1 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f t 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f t 3 1 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc              PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 27 0 t f f f f t 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc              PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 27 0 t f f f f t 3 1 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class             PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 28 0 t f f f f t 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class             PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f t n 3 1 _null_ _null_ ));
 DESCR("");
 
 
@@ -159,4 +161,16 @@ DESCR("");
 #define                  RELPERSISTENCE_UNLOGGED       'u'             /* unlogged permanent table */
 #define                  RELPERSISTENCE_TEMP           't'             /* temporary table */
 
+/* default selection for replica identity (primary key or nothing) */
+#define                  REPLICA_IDENTITY_DEFAULT      'd'
+/* no replica identity is logged for this relation */
+#define                  REPLICA_IDENTITY_NOTHING      'n'
+/* all columns are loged as replica identity */
+#define                  REPLICA_IDENTITY_FULL         'f'
+/*
+ * an explicitly chosen candidate key's columns are used as identity;
+ * will still be set if the index has been dropped, in that case it
+ * has the same meaning as 'd'
+ */
+#define                  REPLICA_IDENTITY_INDEX        'i'
 #endif   /* PG_CLASS_H */
index 8d1d415939159d0b137c514a4630adde4a3827f7..b31d9d764363092d20a0e0556cd1d497268b8e38 100644 (file)
@@ -42,6 +42,7 @@ CATALOG(pg_index,2610) BKI_WITHOUT_OIDS BKI_SCHEMA_MACRO
        bool            indcheckxmin;   /* must we wait for xmin to be old? */
        bool            indisready;             /* is this index ready for inserts? */
        bool            indislive;              /* is this index alive at all? */
+       bool            indisreplident; /* is this index the identity for replication? */
 
        /* variable-length fields start here, but we allow direct access to indkey */
        int2vector      indkey;                 /* column numbers of indexed cols, or 0 */
@@ -69,7 +70,7 @@ typedef FormData_pg_index *Form_pg_index;
  *             compiler constants for pg_index
  * ----------------
  */
-#define Natts_pg_index                                 18
+#define Natts_pg_index                                 19
 #define Anum_pg_index_indexrelid               1
 #define Anum_pg_index_indrelid                 2
 #define Anum_pg_index_indnatts                 3
@@ -82,12 +83,13 @@ typedef FormData_pg_index *Form_pg_index;
 #define Anum_pg_index_indcheckxmin             10
 #define Anum_pg_index_indisready               11
 #define Anum_pg_index_indislive                        12
-#define Anum_pg_index_indkey                   13
-#define Anum_pg_index_indcollation             14
-#define Anum_pg_index_indclass                 15
-#define Anum_pg_index_indoption                        16
-#define Anum_pg_index_indexprs                 17
-#define Anum_pg_index_indpred                  18
+#define Anum_pg_index_indisreplident   13
+#define Anum_pg_index_indkey                   14
+#define Anum_pg_index_indcollation             15
+#define Anum_pg_index_indclass                 16
+#define Anum_pg_index_indoption                        17
+#define Anum_pg_index_indexprs                 18
+#define Anum_pg_index_indpred                  19
 
 /*
  * Index AMs that support ordered scans must support these two indoption
index 78368c63c1b83733aa3354448c098d9fbb5bb55b..fc6b1d7dbd1a2865e3f75c7e8fec22fec7f2d40d 100644 (file)
@@ -362,6 +362,7 @@ typedef enum NodeTag
        T_CreateEventTrigStmt,
        T_AlterEventTrigStmt,
        T_RefreshMatViewStmt,
+       T_ReplicaIdentityStmt,
 
        /*
         * TAGS FOR PARSE TREE NODES (parsenodes.h)
index e5235cbf40f317bccaa835f75943f26a38db0b19..952fbb30dd2e1feac0bcc37bbb3b4e4caa925833 100644 (file)
@@ -1284,9 +1284,17 @@ typedef enum AlterTableType
        AT_DropInherit,                         /* NO INHERIT parent */
        AT_AddOf,                                       /* OF <type_name> */
        AT_DropOf,                                      /* NOT OF */
+       AT_ReplicaIdentity,                     /* REPLICA IDENTITY */
        AT_GenericOptions                       /* OPTIONS (...) */
 } AlterTableType;
 
+typedef struct ReplicaIdentityStmt
+{
+       NodeTag         type;
+       char            identity_type;
+       char       *name;
+} ReplicaIdentityStmt;
+
 typedef struct AlterTableCmd   /* one subcommand of an ALTER TABLE */
 {
        NodeTag         type;
index 589c9a81b69f08d5511e9419eddb66adac91e6a2..21d5871454b20dacf326fc5f285503f827091011 100644 (file)
@@ -110,6 +110,13 @@ typedef struct RelationData
        MemoryContext rd_rulescxt;      /* private memory cxt for rd_rules, if any */
        TriggerDesc *trigdesc;          /* Trigger info, or NULL if rel has none */
 
+       /*
+        * The index chosen as the relation's replication identity or
+        * InvalidOid. Only set correctly if RelationGetIndexList has been
+        * called/rd_indexvalid > 0.
+        */
+       Oid rd_replidindex;
+
        /*
         * rd_options is set whenever rd_rel is loaded into the relcache entry.
         * Note that you can NOT look into rd_rel for this data.  NULL means "use
diff --git a/src/test/regress/expected/replica_identity.out b/src/test/regress/expected/replica_identity.out
new file mode 100644 (file)
index 0000000..60b866a
--- /dev/null
@@ -0,0 +1,183 @@
+CREATE TABLE test_replica_identity (
+       id serial primary key,
+       keya text not null,
+       keyb text not null,
+       nonkey text,
+       CONSTRAINT test_replica_identity_unique_defer UNIQUE (keya, keyb) DEFERRABLE,
+       CONSTRAINT test_replica_identity_unique_nondefer UNIQUE (keya, keyb)
+);
+CREATE TABLE test_replica_identity_othertable (id serial primary key);
+CREATE INDEX test_replica_identity_keyab ON test_replica_identity (keya, keyb);
+CREATE UNIQUE INDEX test_replica_identity_keyab_key ON test_replica_identity (keya, keyb);
+CREATE UNIQUE INDEX test_replica_identity_nonkey ON test_replica_identity (keya, nonkey);
+CREATE INDEX test_replica_identity_hash ON test_replica_identity USING hash (nonkey);
+CREATE UNIQUE INDEX test_replica_identity_expr ON test_replica_identity (keya, keyb, (3));
+CREATE UNIQUE INDEX test_replica_identity_partial ON test_replica_identity (keya, keyb) WHERE keyb != '3';
+-- default is 'd'/DEFAULT for user created tables
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+ relreplident 
+--------------
+ d
+(1 row)
+
+-- but 'none' for system tables
+SELECT relreplident FROM pg_class WHERE oid = 'pg_class'::regclass;
+ relreplident 
+--------------
+ n
+(1 row)
+
+SELECT relreplident FROM pg_class WHERE oid = 'pg_constraint'::regclass;
+ relreplident 
+--------------
+ n
+(1 row)
+
+----
+-- Make sure we detect inelegible indexes
+----
+-- fail, not unique
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_keyab;
+ERROR:  cannot use non-unique index "test_replica_identity_keyab" as replica identity
+-- fail, not a candidate key, nullable column
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_nonkey;
+ERROR:  index "test_replica_identity_nonkey" cannot be used as replica identity because column "nonkey" is nullable
+-- fail, hash indexes cannot do uniqueness
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_hash;
+ERROR:  cannot use non-unique index "test_replica_identity_hash" as replica identity
+-- fail, expression index
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_expr;
+ERROR:  cannot use expression index "test_replica_identity_expr" as replica identity
+-- fail, partial index
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_partial;
+ERROR:  cannot use partial index "test_replica_identity_partial" as replica identity
+-- fail, not our index
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_othertable_pkey;
+ERROR:  "test_replica_identity_othertable_pkey" is not an index for table "test_replica_identity"
+-- fail, deferrable
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_unique_defer;
+ERROR:  cannot use non-immediate index "test_replica_identity_unique_defer" as replica identity
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+ relreplident 
+--------------
+ d
+(1 row)
+
+----
+-- Make sure index cases succeeed
+----
+-- succeed, primary key
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_pkey;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+ relreplident 
+--------------
+ i
+(1 row)
+
+\d test_replica_identity
+                         Table "public.test_replica_identity"
+ Column |  Type   |                             Modifiers                              
+--------+---------+--------------------------------------------------------------------
+ id     | integer | not null default nextval('test_replica_identity_id_seq'::regclass)
+ keya   | text    | not null
+ keyb   | text    | not null
+ nonkey | text    | 
+Indexes:
+    "test_replica_identity_pkey" PRIMARY KEY, btree (id) REPLICA IDENTITY
+    "test_replica_identity_expr" UNIQUE, btree (keya, keyb, (3))
+    "test_replica_identity_keyab_key" UNIQUE, btree (keya, keyb)
+    "test_replica_identity_nonkey" UNIQUE, btree (keya, nonkey)
+    "test_replica_identity_partial" UNIQUE, btree (keya, keyb) WHERE keyb <> '3'::text
+    "test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE
+    "test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb)
+    "test_replica_identity_hash" hash (nonkey)
+    "test_replica_identity_keyab" btree (keya, keyb)
+
+-- succeed, nondeferrable unique constraint over nonullable cols
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_unique_nondefer;
+-- succeed unique index over nonnullable cols
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_keyab_key;
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_keyab_key;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+ relreplident 
+--------------
+ i
+(1 row)
+
+\d test_replica_identity
+                         Table "public.test_replica_identity"
+ Column |  Type   |                             Modifiers                              
+--------+---------+--------------------------------------------------------------------
+ id     | integer | not null default nextval('test_replica_identity_id_seq'::regclass)
+ keya   | text    | not null
+ keyb   | text    | not null
+ nonkey | text    | 
+Indexes:
+    "test_replica_identity_pkey" PRIMARY KEY, btree (id)
+    "test_replica_identity_expr" UNIQUE, btree (keya, keyb, (3))
+    "test_replica_identity_keyab_key" UNIQUE, btree (keya, keyb) REPLICA IDENTITY
+    "test_replica_identity_nonkey" UNIQUE, btree (keya, nonkey)
+    "test_replica_identity_partial" UNIQUE, btree (keya, keyb) WHERE keyb <> '3'::text
+    "test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE
+    "test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb)
+    "test_replica_identity_hash" hash (nonkey)
+    "test_replica_identity_keyab" btree (keya, keyb)
+
+SELECT count(*) FROM pg_index WHERE indrelid = 'test_replica_identity'::regclass AND indisreplident;
+ count 
+-------
+     1
+(1 row)
+
+----
+-- Make sure non index cases work
+----
+ALTER TABLE test_replica_identity REPLICA IDENTITY DEFAULT;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+ relreplident 
+--------------
+ d
+(1 row)
+
+SELECT count(*) FROM pg_index WHERE indrelid = 'test_replica_identity'::regclass AND indisreplident;
+ count 
+-------
+     0
+(1 row)
+
+ALTER TABLE test_replica_identity REPLICA IDENTITY FULL;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+ relreplident 
+--------------
+ f
+(1 row)
+
+\d test_replica_identity
+                         Table "public.test_replica_identity"
+ Column |  Type   |                             Modifiers                              
+--------+---------+--------------------------------------------------------------------
+ id     | integer | not null default nextval('test_replica_identity_id_seq'::regclass)
+ keya   | text    | not null
+ keyb   | text    | not null
+ nonkey | text    | 
+Indexes:
+    "test_replica_identity_pkey" PRIMARY KEY, btree (id)
+    "test_replica_identity_expr" UNIQUE, btree (keya, keyb, (3))
+    "test_replica_identity_keyab_key" UNIQUE, btree (keya, keyb)
+    "test_replica_identity_nonkey" UNIQUE, btree (keya, nonkey)
+    "test_replica_identity_partial" UNIQUE, btree (keya, keyb) WHERE keyb <> '3'::text
+    "test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE
+    "test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb)
+    "test_replica_identity_hash" hash (nonkey)
+    "test_replica_identity_keyab" btree (keya, keyb)
+Replica Identity: FULL
+
+ALTER TABLE test_replica_identity REPLICA IDENTITY NOTHING;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+ relreplident 
+--------------
+ n
+(1 row)
+
+DROP TABLE test_replica_identity;
+DROP TABLE test_replica_identity_othertable;
index 1c1491c3f3ee3b23fd04902a5ad6801192233a90..5758b07fa42828740f8bc6fd8152e44d5656e6e3 100644 (file)
@@ -83,7 +83,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: privileges security_label collate matview lock
+test: privileges security_label collate matview lock replica_identity
 
 # ----------
 # Another group of parallel tests
index c4d451ab00c4c443f8705faea243f0a868340994..78348f5f86588e51a7dc004db52e4937ef113e47 100644 (file)
@@ -98,6 +98,7 @@ test: security_label
 test: collate
 test: matview
 test: lock
+test: replica_identity
 test: alter_generic
 test: misc
 test: psql
diff --git a/src/test/regress/sql/replica_identity.sql b/src/test/regress/sql/replica_identity.sql
new file mode 100644 (file)
index 0000000..9d2e9a6
--- /dev/null
@@ -0,0 +1,79 @@
+CREATE TABLE test_replica_identity (
+       id serial primary key,
+       keya text not null,
+       keyb text not null,
+       nonkey text,
+       CONSTRAINT test_replica_identity_unique_defer UNIQUE (keya, keyb) DEFERRABLE,
+       CONSTRAINT test_replica_identity_unique_nondefer UNIQUE (keya, keyb)
+);
+
+CREATE TABLE test_replica_identity_othertable (id serial primary key);
+
+CREATE INDEX test_replica_identity_keyab ON test_replica_identity (keya, keyb);
+CREATE UNIQUE INDEX test_replica_identity_keyab_key ON test_replica_identity (keya, keyb);
+CREATE UNIQUE INDEX test_replica_identity_nonkey ON test_replica_identity (keya, nonkey);
+CREATE INDEX test_replica_identity_hash ON test_replica_identity USING hash (nonkey);
+CREATE UNIQUE INDEX test_replica_identity_expr ON test_replica_identity (keya, keyb, (3));
+CREATE UNIQUE INDEX test_replica_identity_partial ON test_replica_identity (keya, keyb) WHERE keyb != '3';
+
+-- default is 'd'/DEFAULT for user created tables
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+-- but 'none' for system tables
+SELECT relreplident FROM pg_class WHERE oid = 'pg_class'::regclass;
+SELECT relreplident FROM pg_class WHERE oid = 'pg_constraint'::regclass;
+
+----
+-- Make sure we detect inelegible indexes
+----
+
+-- fail, not unique
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_keyab;
+-- fail, not a candidate key, nullable column
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_nonkey;
+-- fail, hash indexes cannot do uniqueness
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_hash;
+-- fail, expression index
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_expr;
+-- fail, partial index
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_partial;
+-- fail, not our index
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_othertable_pkey;
+-- fail, deferrable
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_unique_defer;
+
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+
+----
+-- Make sure index cases succeeed
+----
+
+-- succeed, primary key
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_pkey;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+\d test_replica_identity
+
+-- succeed, nondeferrable unique constraint over nonullable cols
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_unique_nondefer;
+
+-- succeed unique index over nonnullable cols
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_keyab_key;
+ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_keyab_key;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+\d test_replica_identity
+SELECT count(*) FROM pg_index WHERE indrelid = 'test_replica_identity'::regclass AND indisreplident;
+
+----
+-- Make sure non index cases work
+----
+ALTER TABLE test_replica_identity REPLICA IDENTITY DEFAULT;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+SELECT count(*) FROM pg_index WHERE indrelid = 'test_replica_identity'::regclass AND indisreplident;
+
+ALTER TABLE test_replica_identity REPLICA IDENTITY FULL;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+\d test_replica_identity
+ALTER TABLE test_replica_identity REPLICA IDENTITY NOTHING;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
+
+DROP TABLE test_replica_identity;
+DROP TABLE test_replica_identity_othertable;