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>
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>
</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>
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)
/* 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);
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,
case AT_DisableTrigUser:
case AT_AddIndex: /* from ADD CONSTRAINT */
case AT_AddIndexConstraint:
+ case AT_ReplicaIdentity:
cmd_lockmode = ShareRowExclusiveLock;
break;
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:
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;
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 (...)
*/
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)
{
case T_RefreshMatViewStmt:
retval = _copyRefreshMatViewStmt(from);
break;
+ case T_ReplicaIdentityStmt:
+ retval = _copyReplicaIdentityStmt(from);
+ break;
case T_CreateSeqStmt:
retval = _copyCreateSeqStmt(from);
break;
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)
{
case T_RefreshMatViewStmt:
retval = _equalRefreshMatViewStmt(a, b);
break;
+ case T_ReplicaIdentityStmt:
+ retval = _equalReplicaIdentityStmt(a, b);
+ break;
case T_CreateSeqStmt:
retval = _equalCreateSeqStmt(a, b);
break;
%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
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);
| /* 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; }
;
/* ... 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;
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
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. */
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. */
int i_toastfrozenxid;
int i_relpersistence;
int i_relispopulated;
+ int i_relreplident;
int i_owning_tab;
int i_owning_col;
int i_reltablespace;
* 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
"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, "
"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, "
"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, "
"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, "
"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, "
"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, "
"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, "
"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, "
"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, "
"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, "
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");
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));
i_indnkeys,
i_indkey,
i_indisclustered,
+ i_indisreplident,
i_contype,
i_conname,
i_condeferrable,
* is not.
*/
resetPQExpBuffer(query);
- if (fout->remoteVersion >= 90000)
+ if (fout->remoteVersion >= 90400)
{
/*
* the test on indisready is necessary in 9.2, and harmless in
"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, "
"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, "
"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, "
"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, "
"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, "
"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, "
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");
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));
}
}
+ /*
+ * 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);
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
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 */
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 */
char *reloptions;
char *reloftype;
char relpersistence;
+ char relreplident;
} tableinfo;
bool show_modifiers = false;
bool retval;
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, "
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;
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"
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, "));
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);
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,
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);
}
}
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')
{
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);
}
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 &&
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 201310271
+#define CATALOG_VERSION_NO 201311081
#endif
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 */
* ----------------
*/
-#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
#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
* 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 n 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 n 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 n 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("");
#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 */
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 */
* 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
#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
T_CreateEventTrigStmt,
T_AlterEventTrigStmt,
T_RefreshMatViewStmt,
+ T_ReplicaIdentityStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
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;
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
--- /dev/null
+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;
# ----------
# 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
test: collate
test: matview
test: lock
+test: replica_identity
test: alter_generic
test: misc
test: psql
--- /dev/null
+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;