Commit 07cacba9 authored by Robert Haas's avatar Robert Haas

Add the notion of REPLICA IDENTITY for a table.

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
parent b97ee66c
...@@ -1862,6 +1862,19 @@ ...@@ -1862,6 +1862,19 @@
relations other than some materialized views)</entry> relations other than some materialized views)</entry>
</row> </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> <row>
<entry><structfield>relfrozenxid</structfield></entry> <entry><structfield>relfrozenxid</structfield></entry>
<entry><type>xid</type></entry> <entry><type>xid</type></entry>
...@@ -3657,6 +3670,17 @@ ...@@ -3657,6 +3670,17 @@
</entry> </entry>
</row> </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> <row>
<entry><structfield>indkey</structfield></entry> <entry><structfield>indkey</structfield></entry>
<entry><type>int2vector</type></entry> <entry><type>int2vector</type></entry>
......
...@@ -69,6 +69,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> ...@@ -69,6 +69,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
NOT OF NOT OF
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable> OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</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> <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> ...@@ -579,6 +580,24 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
</listitem> </listitem>
</varlistentry> </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> <varlistentry>
<term><literal>RENAME</literal></term> <term><literal>RENAME</literal></term>
<listitem> <listitem>
......
...@@ -793,6 +793,7 @@ InsertPgClassTuple(Relation pg_class_desc, ...@@ -793,6 +793,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers); values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass); values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated); 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_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid); values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
if (relacl != (Datum) 0) if (relacl != (Datum) 0)
......
...@@ -614,6 +614,7 @@ UpdateIndexRelation(Oid indexoid, ...@@ -614,6 +614,7 @@ UpdateIndexRelation(Oid indexoid,
/* we set isvalid and isready the same way */ /* we set isvalid and isready the same way */
values[Anum_pg_index_indisready - 1] = BoolGetDatum(isvalid); values[Anum_pg_index_indisready - 1] = BoolGetDatum(isvalid);
values[Anum_pg_index_indislive - 1] = BoolGetDatum(true); 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_indkey - 1] = PointerGetDatum(indkey);
values[Anum_pg_index_indcollation - 1] = PointerGetDatum(indcollation); values[Anum_pg_index_indcollation - 1] = PointerGetDatum(indcollation);
values[Anum_pg_index_indclass - 1] = PointerGetDatum(indclass); values[Anum_pg_index_indclass - 1] = PointerGetDatum(indclass);
......
...@@ -399,6 +399,7 @@ static void ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) ...@@ -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 drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid);
static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode); static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
static void ATExecDropOf(Relation rel, 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 ATExecGenericOptions(Relation rel, List *options);
static void copy_relation_data(SMgrRelation rel, SMgrRelation dst, static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
...@@ -2809,6 +2810,7 @@ AlterTableGetLockLevel(List *cmds) ...@@ -2809,6 +2810,7 @@ AlterTableGetLockLevel(List *cmds)
case AT_DisableTrigUser: case AT_DisableTrigUser:
case AT_AddIndex: /* from ADD CONSTRAINT */ case AT_AddIndex: /* from ADD CONSTRAINT */
case AT_AddIndexConstraint: case AT_AddIndexConstraint:
case AT_ReplicaIdentity:
cmd_lockmode = ShareRowExclusiveLock; cmd_lockmode = ShareRowExclusiveLock;
break; break;
...@@ -3140,6 +3142,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, ...@@ -3140,6 +3142,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
cmd->subtype = AT_ValidateConstraintRecurse; cmd->subtype = AT_ValidateConstraintRecurse;
pass = AT_PASS_MISC; pass = AT_PASS_MISC;
break; 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_EnableTrig: /* ENABLE TRIGGER variants */
case AT_EnableAlwaysTrig: case AT_EnableAlwaysTrig:
case AT_EnableReplicaTrig: case AT_EnableReplicaTrig:
...@@ -3440,6 +3448,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, ...@@ -3440,6 +3448,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_DropOf: case AT_DropOf:
ATExecDropOf(rel, lockmode); ATExecDropOf(rel, lockmode);
break; break;
case AT_ReplicaIdentity:
ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
break;
case AT_GenericOptions: case AT_GenericOptions:
ATExecGenericOptions(rel, (List *) cmd->def); ATExecGenericOptions(rel, (List *) cmd->def);
break; break;
...@@ -10009,6 +10020,217 @@ ATExecDropOf(Relation rel, LOCKMODE lockmode) ...@@ -10009,6 +10020,217 @@ ATExecDropOf(Relation rel, LOCKMODE lockmode)
heap_close(relationRelation, RowExclusiveLock); 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 (...) * ALTER FOREIGN TABLE <name> OPTIONS (...)
*/ */
......
...@@ -3270,6 +3270,17 @@ _copyRefreshMatViewStmt(const RefreshMatViewStmt *from) ...@@ -3270,6 +3270,17 @@ _copyRefreshMatViewStmt(const RefreshMatViewStmt *from)
return newnode; 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 * static CreateSeqStmt *
_copyCreateSeqStmt(const CreateSeqStmt *from) _copyCreateSeqStmt(const CreateSeqStmt *from)
{ {
...@@ -4343,6 +4354,9 @@ copyObject(const void *from) ...@@ -4343,6 +4354,9 @@ copyObject(const void *from)
case T_RefreshMatViewStmt: case T_RefreshMatViewStmt:
retval = _copyRefreshMatViewStmt(from); retval = _copyRefreshMatViewStmt(from);
break; break;
case T_ReplicaIdentityStmt:
retval = _copyReplicaIdentityStmt(from);
break;
case T_CreateSeqStmt: case T_CreateSeqStmt:
retval = _copyCreateSeqStmt(from); retval = _copyCreateSeqStmt(from);
break; break;
......
...@@ -1537,6 +1537,15 @@ _equalRefreshMatViewStmt(const RefreshMatViewStmt *a, const RefreshMatViewStmt * ...@@ -1537,6 +1537,15 @@ _equalRefreshMatViewStmt(const RefreshMatViewStmt *a, const RefreshMatViewStmt *
return true; return true;
} }
static bool
_equalReplicaIdentityStmt(const ReplicaIdentityStmt *a, const ReplicaIdentityStmt *b)
{
COMPARE_SCALAR_FIELD(identity_type);
COMPARE_STRING_FIELD(name);
return true;
}
static bool static bool
_equalCreateSeqStmt(const CreateSeqStmt *a, const CreateSeqStmt *b) _equalCreateSeqStmt(const CreateSeqStmt *a, const CreateSeqStmt *b)
{ {
...@@ -2813,6 +2822,9 @@ equal(const void *a, const void *b) ...@@ -2813,6 +2822,9 @@ equal(const void *a, const void *b)
case T_RefreshMatViewStmt: case T_RefreshMatViewStmt:
retval = _equalRefreshMatViewStmt(a, b); retval = _equalRefreshMatViewStmt(a, b);
break; break;
case T_ReplicaIdentityStmt:
retval = _equalReplicaIdentityStmt(a, b);
break;
case T_CreateSeqStmt: case T_CreateSeqStmt:
retval = _equalCreateSeqStmt(a, b); retval = _equalCreateSeqStmt(a, b);
break; break;
......
...@@ -255,6 +255,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ...@@ -255,6 +255,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> add_drop opt_asc_desc opt_nulls_order %type <ival> add_drop opt_asc_desc opt_nulls_order
%type <node> alter_table_cmd alter_type_cmd opt_collate_clause %type <node> alter_table_cmd alter_type_cmd opt_collate_clause
replica_identity
%type <list> alter_table_cmds alter_type_cmds %type <list> alter_table_cmds alter_type_cmds
%type <dbehavior> opt_drop_behavior %type <dbehavior> opt_drop_behavior
...@@ -2178,6 +2179,14 @@ alter_table_cmd: ...@@ -2178,6 +2179,14 @@ alter_table_cmd:
n->def = (Node *)$2; n->def = (Node *)$2;
$$ = (Node *)n; $$ = (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 | alter_generic_options
{ {
AlterTableCmd *n = makeNode(AlterTableCmd); AlterTableCmd *n = makeNode(AlterTableCmd);
...@@ -2215,6 +2224,37 @@ alter_using: ...@@ -2215,6 +2224,37 @@ alter_using:
| /* EMPTY */ { $$ = NULL; } | /* 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: reloptions:
'(' reloption_list ')' { $$ = $2; } '(' reloption_list ')' { $$ = $2; }
; ;
......
...@@ -1454,6 +1454,7 @@ formrdesc(const char *relationName, Oid relationReltype, ...@@ -1454,6 +1454,7 @@ formrdesc(const char *relationName, Oid relationReltype,
/* ... and they're always populated, too */ /* ... and they're always populated, too */
relation->rd_rel->relispopulated = true; relation->rd_rel->relispopulated = true;
relation->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
relation->rd_rel->relpages = 0; relation->rd_rel->relpages = 0;
relation->rd_rel->reltuples = 0; relation->rd_rel->reltuples = 0;
relation->rd_rel->relallvisible = 0; relation->rd_rel->relallvisible = 0;
...@@ -2664,6 +2665,13 @@ RelationBuildLocalRelation(const char *relname, ...@@ -2664,6 +2665,13 @@ RelationBuildLocalRelation(const char *relname,
else else
rel->rd_rel->relispopulated = true; 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 * Insert relation physical and logical identifiers (OIDs) into the right
* places. For a mapped relation, we set relfilenode to zero and rely on * places. For a mapped relation, we set relfilenode to zero and rely on
...@@ -3462,7 +3470,10 @@ RelationGetIndexList(Relation relation) ...@@ -3462,7 +3470,10 @@ RelationGetIndexList(Relation relation)
ScanKeyData skey; ScanKeyData skey;
HeapTuple htup; HeapTuple htup;
List *result; List *result;
Oid oidIndex; char replident = relation->rd_rel->relreplident;
Oid oidIndex = InvalidOid;
Oid pkeyIndex = InvalidOid;
Oid candidateIndex = InvalidOid;
MemoryContext oldcxt; MemoryContext oldcxt;
/* Quick exit if we already computed the list. */ /* Quick exit if we already computed the list. */
...@@ -3519,17 +3530,45 @@ RelationGetIndexList(Relation relation) ...@@ -3519,17 +3530,45 @@ RelationGetIndexList(Relation relation)
Assert(!isnull); Assert(!isnull);
indclass = (oidvector *) DatumGetPointer(indclassDatum); indclass = (oidvector *) DatumGetPointer(indclassDatum);
/* Check to see if it is a unique, non-partial btree index on OID */ /*
if (IndexIsValid(index) && * Invalid, non-unique, non-immediate or predicate indexes aren't
index->indnatts == 1 && * interesting for neither oid indexes nor replication identity
index->indisunique && index->indimmediate && * 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 && index->indkey.values[0] == ObjectIdAttributeNumber &&
indclass->values[0] == OID_BTREE_OPS_OID && indclass->values[0] == OID_BTREE_OPS_OID)
heap_attisnull(htup, Anum_pg_index_indpred))
oidIndex = index->indexrelid; 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); 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); heap_close(indrel, AccessShareLock);
/* Now save a copy of the completed list in the relcache entry. */ /* Now save a copy of the completed list in the relcache entry. */
......
...@@ -4221,6 +4221,7 @@ getTables(Archive *fout, int *numTables) ...@@ -4221,6 +4221,7 @@ getTables(Archive *fout, int *numTables)
int i_toastfrozenxid; int i_toastfrozenxid;
int i_relpersistence; int i_relpersistence;
int i_relispopulated; int i_relispopulated;
int i_relreplident;
int i_owning_tab; int i_owning_tab;
int i_owning_col; int i_owning_col;
int i_reltablespace; int i_reltablespace;
...@@ -4253,7 +4254,7 @@ getTables(Archive *fout, int *numTables) ...@@ -4253,7 +4254,7 @@ getTables(Archive *fout, int *numTables)
* we cannot correctly identify inherited columns, owned sequences, etc. * 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 * Left join to pick up dependency info linking sequences to their
...@@ -4268,7 +4269,46 @@ getTables(Archive *fout, int *numTables) ...@@ -4268,7 +4269,46 @@ getTables(Archive *fout, int *numTables)
"c.relfrozenxid, tc.oid AS toid, " "c.relfrozenxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, " "tc.relfrozenxid AS tfrozenxid, "
"c.relpersistence, c.relispopulated, " "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, " "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
"d.refobjid AS owning_tab, " "d.refobjid AS owning_tab, "
"d.refobjsubid AS owning_col, " "d.refobjsubid AS owning_col, "
...@@ -4307,7 +4347,7 @@ getTables(Archive *fout, int *numTables) ...@@ -4307,7 +4347,7 @@ getTables(Archive *fout, int *numTables)
"c.relfrozenxid, tc.oid AS toid, " "c.relfrozenxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, " "tc.relfrozenxid AS tfrozenxid, "
"c.relpersistence, 't' as relispopulated, " "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, " "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
"d.refobjid AS owning_tab, " "d.refobjid AS owning_tab, "
"d.refobjsubid AS owning_col, " "d.refobjsubid AS owning_col, "
...@@ -4344,7 +4384,7 @@ getTables(Archive *fout, int *numTables) ...@@ -4344,7 +4384,7 @@ getTables(Archive *fout, int *numTables)
"c.relfrozenxid, tc.oid AS toid, " "c.relfrozenxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, " "tc.relfrozenxid AS tfrozenxid, "
"'p' AS relpersistence, 't' as relispopulated, " "'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, " "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
"d.refobjid AS owning_tab, " "d.refobjid AS owning_tab, "
"d.refobjsubid AS owning_col, " "d.refobjsubid AS owning_col, "
...@@ -4380,7 +4420,7 @@ getTables(Archive *fout, int *numTables) ...@@ -4380,7 +4420,7 @@ getTables(Archive *fout, int *numTables)
"c.relfrozenxid, tc.oid AS toid, " "c.relfrozenxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, " "tc.relfrozenxid AS tfrozenxid, "
"'p' AS relpersistence, 't' as relispopulated, " "'p' AS relpersistence, 't' as relispopulated, "
"c.relpages, " "'d' AS relreplident, c.relpages, "
"NULL AS reloftype, " "NULL AS reloftype, "
"d.refobjid AS owning_tab, " "d.refobjid AS owning_tab, "
"d.refobjsubid AS owning_col, " "d.refobjsubid AS owning_col, "
...@@ -4416,7 +4456,7 @@ getTables(Archive *fout, int *numTables) ...@@ -4416,7 +4456,7 @@ getTables(Archive *fout, int *numTables)
"c.relfrozenxid, tc.oid AS toid, " "c.relfrozenxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, " "tc.relfrozenxid AS tfrozenxid, "
"'p' AS relpersistence, 't' as relispopulated, " "'p' AS relpersistence, 't' as relispopulated, "
"c.relpages, " "'d' AS relreplident, c.relpages, "
"NULL AS reloftype, " "NULL AS reloftype, "
"d.refobjid AS owning_tab, " "d.refobjid AS owning_tab, "
"d.refobjsubid AS owning_col, " "d.refobjsubid AS owning_col, "
...@@ -4453,7 +4493,7 @@ getTables(Archive *fout, int *numTables) ...@@ -4453,7 +4493,7 @@ getTables(Archive *fout, int *numTables)
"0 AS toid, " "0 AS toid, "
"0 AS tfrozenxid, " "0 AS tfrozenxid, "
"'p' AS relpersistence, 't' as relispopulated, " "'p' AS relpersistence, 't' as relispopulated, "
"relpages, " "'d' AS relreplident, relpages, "
"NULL AS reloftype, " "NULL AS reloftype, "
"d.refobjid AS owning_tab, " "d.refobjid AS owning_tab, "
"d.refobjsubid AS owning_col, " "d.refobjsubid AS owning_col, "
...@@ -4489,7 +4529,7 @@ getTables(Archive *fout, int *numTables) ...@@ -4489,7 +4529,7 @@ getTables(Archive *fout, int *numTables)
"0 AS toid, " "0 AS toid, "
"0 AS tfrozenxid, " "0 AS tfrozenxid, "
"'p' AS relpersistence, 't' as relispopulated, " "'p' AS relpersistence, 't' as relispopulated, "
"relpages, " "'d' AS relreplident, relpages, "
"NULL AS reloftype, " "NULL AS reloftype, "
"d.refobjid AS owning_tab, " "d.refobjid AS owning_tab, "
"d.refobjsubid AS owning_col, " "d.refobjsubid AS owning_col, "
...@@ -4521,7 +4561,7 @@ getTables(Archive *fout, int *numTables) ...@@ -4521,7 +4561,7 @@ getTables(Archive *fout, int *numTables)
"0 AS toid, " "0 AS toid, "
"0 AS tfrozenxid, " "0 AS tfrozenxid, "
"'p' AS relpersistence, 't' as relispopulated, " "'p' AS relpersistence, 't' as relispopulated, "
"relpages, " "'d' AS relreplident, relpages, "
"NULL AS reloftype, " "NULL AS reloftype, "
"NULL::oid AS owning_tab, " "NULL::oid AS owning_tab, "
"NULL::int4 AS owning_col, " "NULL::int4 AS owning_col, "
...@@ -4548,7 +4588,7 @@ getTables(Archive *fout, int *numTables) ...@@ -4548,7 +4588,7 @@ getTables(Archive *fout, int *numTables)
"0 AS toid, " "0 AS toid, "
"0 AS tfrozenxid, " "0 AS tfrozenxid, "
"'p' AS relpersistence, 't' as relispopulated, " "'p' AS relpersistence, 't' as relispopulated, "
"relpages, " "'d' AS relreplident, relpages, "
"NULL AS reloftype, " "NULL AS reloftype, "
"NULL::oid AS owning_tab, " "NULL::oid AS owning_tab, "
"NULL::int4 AS owning_col, " "NULL::int4 AS owning_col, "
...@@ -4585,7 +4625,7 @@ getTables(Archive *fout, int *numTables) ...@@ -4585,7 +4625,7 @@ getTables(Archive *fout, int *numTables)
"0 AS toid, " "0 AS toid, "
"0 AS tfrozenxid, " "0 AS tfrozenxid, "
"'p' AS relpersistence, 't' as relispopulated, " "'p' AS relpersistence, 't' as relispopulated, "
"0 AS relpages, " "'d' AS relreplident, 0 AS relpages, "
"NULL AS reloftype, " "NULL AS reloftype, "
"NULL::oid AS owning_tab, " "NULL::oid AS owning_tab, "
"NULL::int4 AS owning_col, " "NULL::int4 AS owning_col, "
...@@ -4634,6 +4674,7 @@ getTables(Archive *fout, int *numTables) ...@@ -4634,6 +4674,7 @@ getTables(Archive *fout, int *numTables)
i_toastfrozenxid = PQfnumber(res, "tfrozenxid"); i_toastfrozenxid = PQfnumber(res, "tfrozenxid");
i_relpersistence = PQfnumber(res, "relpersistence"); i_relpersistence = PQfnumber(res, "relpersistence");
i_relispopulated = PQfnumber(res, "relispopulated"); i_relispopulated = PQfnumber(res, "relispopulated");
i_relreplident = PQfnumber(res, "relreplident");
i_relpages = PQfnumber(res, "relpages"); i_relpages = PQfnumber(res, "relpages");
i_owning_tab = PQfnumber(res, "owning_tab"); i_owning_tab = PQfnumber(res, "owning_tab");
i_owning_col = PQfnumber(res, "owning_col"); i_owning_col = PQfnumber(res, "owning_col");
...@@ -4678,6 +4719,7 @@ getTables(Archive *fout, int *numTables) ...@@ -4678,6 +4719,7 @@ getTables(Archive *fout, int *numTables)
tblinfo[i].hastriggers = (strcmp(PQgetvalue(res, i, i_relhastriggers), "t") == 0); tblinfo[i].hastriggers = (strcmp(PQgetvalue(res, i, i_relhastriggers), "t") == 0);
tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "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].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].relpages = atoi(PQgetvalue(res, i, i_relpages));
tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid)); tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid));
tblinfo[i].toast_oid = atooid(PQgetvalue(res, i, i_toastoid)); tblinfo[i].toast_oid = atooid(PQgetvalue(res, i, i_toastoid));
...@@ -4863,6 +4905,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) ...@@ -4863,6 +4905,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_indnkeys, i_indnkeys,
i_indkey, i_indkey,
i_indisclustered, i_indisclustered,
i_indisreplident,
i_contype, i_contype,
i_conname, i_conname,
i_condeferrable, i_condeferrable,
...@@ -4909,7 +4952,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) ...@@ -4909,7 +4952,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
* is not. * is not.
*/ */
resetPQExpBuffer(query); resetPQExpBuffer(query);
if (fout->remoteVersion >= 90000) if (fout->remoteVersion >= 90400)
{ {
/* /*
* the test on indisready is necessary in 9.2, and harmless in * the test on indisready is necessary in 9.2, and harmless in
...@@ -4921,7 +4964,38 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) ...@@ -4921,7 +4964,38 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, " "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
"t.relnatts AS indnkeys, " "t.relnatts AS indnkeys, "
"i.indkey, i.indisclustered, " "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.contype, c.conname, "
"c.condeferrable, c.condeferred, " "c.condeferrable, c.condeferred, "
"c.tableoid AS contableoid, " "c.tableoid AS contableoid, "
...@@ -4948,7 +5022,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) ...@@ -4948,7 +5022,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, " "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
"t.relnatts AS indnkeys, " "t.relnatts AS indnkeys, "
"i.indkey, i.indisclustered, " "i.indkey, i.indisclustered, "
"t.relpages, " "false AS indisreplident, t.relpages, "
"c.contype, c.conname, " "c.contype, c.conname, "
"c.condeferrable, c.condeferred, " "c.condeferrable, c.condeferred, "
"c.tableoid AS contableoid, " "c.tableoid AS contableoid, "
...@@ -4978,7 +5052,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) ...@@ -4978,7 +5052,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, " "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
"t.relnatts AS indnkeys, " "t.relnatts AS indnkeys, "
"i.indkey, i.indisclustered, " "i.indkey, i.indisclustered, "
"t.relpages, " "false AS indisreplident, t.relpages, "
"c.contype, c.conname, " "c.contype, c.conname, "
"c.condeferrable, c.condeferred, " "c.condeferrable, c.condeferred, "
"c.tableoid AS contableoid, " "c.tableoid AS contableoid, "
...@@ -5007,7 +5081,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) ...@@ -5007,7 +5081,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, " "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
"t.relnatts AS indnkeys, " "t.relnatts AS indnkeys, "
"i.indkey, i.indisclustered, " "i.indkey, i.indisclustered, "
"t.relpages, " "false AS indisreplident, t.relpages, "
"c.contype, c.conname, " "c.contype, c.conname, "
"c.condeferrable, c.condeferred, " "c.condeferrable, c.condeferred, "
"c.tableoid AS contableoid, " "c.tableoid AS contableoid, "
...@@ -5036,7 +5110,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) ...@@ -5036,7 +5110,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
"pg_get_indexdef(i.indexrelid) AS indexdef, " "pg_get_indexdef(i.indexrelid) AS indexdef, "
"t.relnatts AS indnkeys, " "t.relnatts AS indnkeys, "
"i.indkey, false AS indisclustered, " "i.indkey, false AS indisclustered, "
"t.relpages, " "false AS indisreplident, t.relpages, "
"CASE WHEN i.indisprimary THEN 'p'::char " "CASE WHEN i.indisprimary THEN 'p'::char "
"ELSE '0'::char END AS contype, " "ELSE '0'::char END AS contype, "
"t.relname AS conname, " "t.relname AS conname, "
...@@ -5063,7 +5137,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) ...@@ -5063,7 +5137,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
"pg_get_indexdef(i.indexrelid) AS indexdef, " "pg_get_indexdef(i.indexrelid) AS indexdef, "
"t.relnatts AS indnkeys, " "t.relnatts AS indnkeys, "
"i.indkey, false AS indisclustered, " "i.indkey, false AS indisclustered, "
"t.relpages, " "false AS indisreplident, t.relpages, "
"CASE WHEN i.indisprimary THEN 'p'::char " "CASE WHEN i.indisprimary THEN 'p'::char "
"ELSE '0'::char END AS contype, " "ELSE '0'::char END AS contype, "
"t.relname AS conname, " "t.relname AS conname, "
...@@ -5092,6 +5166,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) ...@@ -5092,6 +5166,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_indnkeys = PQfnumber(res, "indnkeys"); i_indnkeys = PQfnumber(res, "indnkeys");
i_indkey = PQfnumber(res, "indkey"); i_indkey = PQfnumber(res, "indkey");
i_indisclustered = PQfnumber(res, "indisclustered"); i_indisclustered = PQfnumber(res, "indisclustered");
i_indisreplident = PQfnumber(res, "indisreplident");
i_relpages = PQfnumber(res, "relpages"); i_relpages = PQfnumber(res, "relpages");
i_contype = PQfnumber(res, "contype"); i_contype = PQfnumber(res, "contype");
i_conname = PQfnumber(res, "conname"); i_conname = PQfnumber(res, "conname");
...@@ -5135,6 +5210,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) ...@@ -5135,6 +5210,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
parseOidArray(PQgetvalue(res, j, i_indkey), parseOidArray(PQgetvalue(res, j, i_indkey),
indxinfo[j].indkeys, INDEX_MAX_KEYS); indxinfo[j].indkeys, INDEX_MAX_KEYS);
indxinfo[j].indisclustered = (PQgetvalue(res, j, i_indisclustered)[0] == 't'); 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)); indxinfo[j].relpages = atoi(PQgetvalue(res, j, i_relpages));
contype = *(PQgetvalue(res, j, i_contype)); contype = *(PQgetvalue(res, j, i_contype));
...@@ -13408,6 +13484,28 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) ...@@ -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) if (binary_upgrade)
binary_upgrade_extension_member(q, &tbinfo->dobj, labelq->data); binary_upgrade_extension_member(q, &tbinfo->dobj, labelq->data);
...@@ -13579,6 +13677,15 @@ dumpIndex(Archive *fout, IndxInfo *indxinfo) ...@@ -13579,6 +13677,15 @@ dumpIndex(Archive *fout, IndxInfo *indxinfo)
fmtId(indxinfo->dobj.name)); 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 * DROP must be fully qualified in case same name appears in
* pg_catalog * pg_catalog
......
...@@ -237,6 +237,7 @@ typedef struct _tableInfo ...@@ -237,6 +237,7 @@ typedef struct _tableInfo
char relkind; char relkind;
char relpersistence; /* relation persistence */ char relpersistence; /* relation persistence */
bool relispopulated; /* relation is populated */ bool relispopulated; /* relation is populated */
bool relreplident; /* replica identifier */
char *reltablespace; /* relation tablespace */ char *reltablespace; /* relation tablespace */
char *reloptions; /* options specified by WITH (...) */ char *reloptions; /* options specified by WITH (...) */
char *checkoption; /* WITH CHECK OPTION */ char *checkoption; /* WITH CHECK OPTION */
...@@ -315,6 +316,7 @@ typedef struct _indxInfo ...@@ -315,6 +316,7 @@ typedef struct _indxInfo
int indnkeys; int indnkeys;
Oid *indkeys; Oid *indkeys;
bool indisclustered; bool indisclustered;
bool indisreplident;
/* if there is an associated constraint object, its dumpId: */ /* if there is an associated constraint object, its dumpId: */
DumpId indexconstraint; DumpId indexconstraint;
int relpages; /* relpages of the underlying table */ int relpages; /* relpages of the underlying table */
......
...@@ -1156,6 +1156,7 @@ describeOneTableDetails(const char *schemaname, ...@@ -1156,6 +1156,7 @@ describeOneTableDetails(const char *schemaname,
char *reloptions; char *reloptions;
char *reloftype; char *reloftype;
char relpersistence; char relpersistence;
char relreplident;
} tableinfo; } tableinfo;
bool show_modifiers = false; bool show_modifiers = false;
bool retval; bool retval;
...@@ -1171,7 +1172,24 @@ describeOneTableDetails(const char *schemaname, ...@@ -1171,7 +1172,24 @@ describeOneTableDetails(const char *schemaname,
initPQExpBuffer(&tmpbuf); initPQExpBuffer(&tmpbuf);
/* Get general table info */ /* 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, printfPQExpBuffer(&buf,
"SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, " "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
...@@ -1276,6 +1294,8 @@ describeOneTableDetails(const char *schemaname, ...@@ -1276,6 +1294,8 @@ describeOneTableDetails(const char *schemaname,
pg_strdup(PQgetvalue(res, 0, 8)) : NULL; pg_strdup(PQgetvalue(res, 0, 8)) : NULL;
tableinfo.relpersistence = (pset.sversion >= 90100) ? tableinfo.relpersistence = (pset.sversion >= 90100) ?
*(PQgetvalue(res, 0, 9)) : 0; *(PQgetvalue(res, 0, 9)) : 0;
tableinfo.relreplident = (pset.sversion >= 90400) ?
*(PQgetvalue(res, 0, 10)) : 'd';
PQclear(res); PQclear(res);
res = NULL; res = NULL;
...@@ -1589,6 +1609,12 @@ describeOneTableDetails(const char *schemaname, ...@@ -1589,6 +1609,12 @@ describeOneTableDetails(const char *schemaname,
else else
appendPQExpBuffer(&buf, appendPQExpBuffer(&buf,
" false AS condeferrable, false AS condeferred,\n"); " 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, " appendPQExpBuffer(&buf, " a.amname, c2.relname, "
"pg_catalog.pg_get_expr(i.indpred, i.indrelid, true)\n" "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" "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, ...@@ -1612,9 +1638,10 @@ describeOneTableDetails(const char *schemaname,
char *indisvalid = PQgetvalue(result, 0, 3); char *indisvalid = PQgetvalue(result, 0, 3);
char *deferrable = PQgetvalue(result, 0, 4); char *deferrable = PQgetvalue(result, 0, 4);
char *deferred = PQgetvalue(result, 0, 5); char *deferred = PQgetvalue(result, 0, 5);
char *indamname = PQgetvalue(result, 0, 6); char *indisidentity = PQgetvalue(result, 0, 6);
char *indtable = PQgetvalue(result, 0, 7); char *indamname = PQgetvalue(result, 0, 7);
char *indpred = PQgetvalue(result, 0, 8); char *indtable = PQgetvalue(result, 0, 8);
char *indpred = PQgetvalue(result, 0, 9);
if (strcmp(indisprimary, "t") == 0) if (strcmp(indisprimary, "t") == 0)
printfPQExpBuffer(&tmpbuf, _("primary key, ")); printfPQExpBuffer(&tmpbuf, _("primary key, "));
...@@ -1643,6 +1670,9 @@ describeOneTableDetails(const char *schemaname, ...@@ -1643,6 +1670,9 @@ describeOneTableDetails(const char *schemaname,
if (strcmp(deferred, "t") == 0) if (strcmp(deferred, "t") == 0)
appendPQExpBuffer(&tmpbuf, _(", initially deferred")); appendPQExpBuffer(&tmpbuf, _(", initially deferred"));
if (strcmp(indisidentity, "t") == 0)
appendPQExpBuffer(&tmpbuf, _(", replica identity"));
printTableAddFooter(&cont, tmpbuf.data); printTableAddFooter(&cont, tmpbuf.data);
add_tablespace_footer(&cont, tableinfo.relkind, add_tablespace_footer(&cont, tableinfo.relkind,
tableinfo.tablespace, true); tableinfo.tablespace, true);
...@@ -1713,6 +1743,10 @@ describeOneTableDetails(const char *schemaname, ...@@ -1713,6 +1743,10 @@ describeOneTableDetails(const char *schemaname,
appendPQExpBuffer(&buf, appendPQExpBuffer(&buf,
"null AS constraintdef, null AS contype, " "null AS constraintdef, null AS contype, "
"false AS condeferrable, false AS condeferred"); "false AS condeferrable, false AS condeferred");
if (pset.sversion >= 90400)
appendPQExpBuffer(&buf, ", i.indisreplident");
else
appendPQExpBuffer(&buf, ", false AS indisreplident");
if (pset.sversion >= 80000) if (pset.sversion >= 80000)
appendPQExpBuffer(&buf, ", c2.reltablespace"); appendPQExpBuffer(&buf, ", c2.reltablespace");
appendPQExpBuffer(&buf, appendPQExpBuffer(&buf,
...@@ -1783,12 +1817,15 @@ describeOneTableDetails(const char *schemaname, ...@@ -1783,12 +1817,15 @@ describeOneTableDetails(const char *schemaname,
if (strcmp(PQgetvalue(result, i, 4), "t") != 0) if (strcmp(PQgetvalue(result, i, 4), "t") != 0)
appendPQExpBuffer(&buf, " INVALID"); appendPQExpBuffer(&buf, " INVALID");
if (strcmp(PQgetvalue(result, i, 10), "t") == 0)
appendPQExpBuffer(&buf, " REPLICA IDENTITY");
printTableAddFooter(&cont, buf.data); printTableAddFooter(&cont, buf.data);
/* Print tablespace of the index on the same line */ /* Print tablespace of the index on the same line */
if (pset.sversion >= 80000) if (pset.sversion >= 80000)
add_tablespace_footer(&cont, 'i', add_tablespace_footer(&cont, 'i',
atooid(PQgetvalue(result, i, 10)), atooid(PQgetvalue(result, i, 11)),
false); false);
} }
} }
...@@ -2273,6 +2310,17 @@ describeOneTableDetails(const char *schemaname, ...@@ -2273,6 +2310,17 @@ describeOneTableDetails(const char *schemaname,
printTableAddFooter(&cont, buf.data); 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 */ /* OIDs, if verbose and not a materialized view */
if (verbose && tableinfo.relkind != 'm') if (verbose && tableinfo.relkind != 'm')
{ {
......
...@@ -1336,7 +1336,7 @@ psql_completion(char *text, int start, int end) ...@@ -1336,7 +1336,7 @@ psql_completion(char *text, int start, int end)
static const char *const list_ALTER2[] = static const char *const list_ALTER2[] =
{"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT", {"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT",
"NO INHERIT", "RENAME", "RESET", "OWNER TO", "SET", "NO INHERIT", "RENAME", "RESET", "OWNER TO", "SET",
"VALIDATE CONSTRAINT", NULL}; "VALIDATE CONSTRAINT", "REPLICA IDENTITY", NULL};
COMPLETE_WITH_LIST(list_ALTER2); COMPLETE_WITH_LIST(list_ALTER2);
} }
...@@ -1581,6 +1581,35 @@ psql_completion(char *text, int start, int end) ...@@ -1581,6 +1581,35 @@ psql_completion(char *text, int start, int end)
COMPLETE_WITH_LIST(list_TABLEOPTIONS); 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 */ /* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
......
...@@ -53,6 +53,6 @@ ...@@ -53,6 +53,6 @@
*/ */
/* yyyymmddN */ /* yyyymmddN */
#define CATALOG_VERSION_NO 201310271 #define CATALOG_VERSION_NO 201311081
#endif #endif
...@@ -66,6 +66,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO ...@@ -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 relhastriggers; /* has (or has had) any TRIGGERs */
bool relhassubclass; /* has (or has had) derived classes */ bool relhassubclass; /* has (or has had) derived classes */
bool relispopulated; /* matview currently holds query results */ 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 relfrozenxid; /* all Xids < this are frozen in this rel */
TransactionId relminmxid; /* all multixacts in this rel are >= this. TransactionId relminmxid; /* all multixacts in this rel are >= this.
* this is really a MultiXactId */ * this is really a MultiXactId */
...@@ -93,7 +94,7 @@ typedef FormData_pg_class *Form_pg_class; ...@@ -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_relname 1
#define Anum_pg_class_relnamespace 2 #define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3 #define Anum_pg_class_reltype 3
...@@ -118,10 +119,11 @@ typedef FormData_pg_class *Form_pg_class; ...@@ -118,10 +119,11 @@ typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relhastriggers 22 #define Anum_pg_class_relhastriggers 22
#define Anum_pg_class_relhassubclass 23 #define Anum_pg_class_relhassubclass 23
#define Anum_pg_class_relispopulated 24 #define Anum_pg_class_relispopulated 24
#define Anum_pg_class_relfrozenxid 25 #define Anum_pg_class_relreplident 25
#define Anum_pg_class_relminmxid 26 #define Anum_pg_class_relfrozenxid 26
#define Anum_pg_class_relacl 27 #define Anum_pg_class_relminmxid 27
#define Anum_pg_class_reloptions 28 #define Anum_pg_class_relacl 28
#define Anum_pg_class_reloptions 29
/* ---------------- /* ----------------
* initial contents of pg_class * initial contents of pg_class
...@@ -136,13 +138,13 @@ typedef FormData_pg_class *Form_pg_class; ...@@ -136,13 +138,13 @@ typedef FormData_pg_class *Form_pg_class;
* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId; * Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
* similarly, "1" in relminmxid stands for FirstMultiXactId * 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(""); 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(""); 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(""); 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(""); DESCR("");
...@@ -159,4 +161,16 @@ DESCR(""); ...@@ -159,4 +161,16 @@ DESCR("");
#define RELPERSISTENCE_UNLOGGED 'u' /* unlogged permanent table */ #define RELPERSISTENCE_UNLOGGED 'u' /* unlogged permanent table */
#define RELPERSISTENCE_TEMP 't' /* temporary 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 */ #endif /* PG_CLASS_H */
...@@ -42,6 +42,7 @@ CATALOG(pg_index,2610) BKI_WITHOUT_OIDS BKI_SCHEMA_MACRO ...@@ -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 indcheckxmin; /* must we wait for xmin to be old? */
bool indisready; /* is this index ready for inserts? */ bool indisready; /* is this index ready for inserts? */
bool indislive; /* is this index alive at all? */ 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 */ /* variable-length fields start here, but we allow direct access to indkey */
int2vector indkey; /* column numbers of indexed cols, or 0 */ int2vector indkey; /* column numbers of indexed cols, or 0 */
...@@ -69,7 +70,7 @@ typedef FormData_pg_index *Form_pg_index; ...@@ -69,7 +70,7 @@ typedef FormData_pg_index *Form_pg_index;
* compiler constants for 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_indexrelid 1
#define Anum_pg_index_indrelid 2 #define Anum_pg_index_indrelid 2
#define Anum_pg_index_indnatts 3 #define Anum_pg_index_indnatts 3
...@@ -82,12 +83,13 @@ typedef FormData_pg_index *Form_pg_index; ...@@ -82,12 +83,13 @@ typedef FormData_pg_index *Form_pg_index;
#define Anum_pg_index_indcheckxmin 10 #define Anum_pg_index_indcheckxmin 10
#define Anum_pg_index_indisready 11 #define Anum_pg_index_indisready 11
#define Anum_pg_index_indislive 12 #define Anum_pg_index_indislive 12
#define Anum_pg_index_indkey 13 #define Anum_pg_index_indisreplident 13
#define Anum_pg_index_indcollation 14 #define Anum_pg_index_indkey 14
#define Anum_pg_index_indclass 15 #define Anum_pg_index_indcollation 15
#define Anum_pg_index_indoption 16 #define Anum_pg_index_indclass 16
#define Anum_pg_index_indexprs 17 #define Anum_pg_index_indoption 17
#define Anum_pg_index_indpred 18 #define Anum_pg_index_indexprs 18
#define Anum_pg_index_indpred 19
/* /*
* Index AMs that support ordered scans must support these two indoption * Index AMs that support ordered scans must support these two indoption
......
...@@ -362,6 +362,7 @@ typedef enum NodeTag ...@@ -362,6 +362,7 @@ typedef enum NodeTag
T_CreateEventTrigStmt, T_CreateEventTrigStmt,
T_AlterEventTrigStmt, T_AlterEventTrigStmt,
T_RefreshMatViewStmt, T_RefreshMatViewStmt,
T_ReplicaIdentityStmt,
/* /*
* TAGS FOR PARSE TREE NODES (parsenodes.h) * TAGS FOR PARSE TREE NODES (parsenodes.h)
......
...@@ -1284,9 +1284,17 @@ typedef enum AlterTableType ...@@ -1284,9 +1284,17 @@ typedef enum AlterTableType
AT_DropInherit, /* NO INHERIT parent */ AT_DropInherit, /* NO INHERIT parent */
AT_AddOf, /* OF <type_name> */ AT_AddOf, /* OF <type_name> */
AT_DropOf, /* NOT OF */ AT_DropOf, /* NOT OF */
AT_ReplicaIdentity, /* REPLICA IDENTITY */
AT_GenericOptions /* OPTIONS (...) */ AT_GenericOptions /* OPTIONS (...) */
} AlterTableType; } AlterTableType;
typedef struct ReplicaIdentityStmt
{
NodeTag type;
char identity_type;
char *name;
} ReplicaIdentityStmt;
typedef struct AlterTableCmd /* one subcommand of an ALTER TABLE */ typedef struct AlterTableCmd /* one subcommand of an ALTER TABLE */
{ {
NodeTag type; NodeTag type;
......
...@@ -110,6 +110,13 @@ typedef struct RelationData ...@@ -110,6 +110,13 @@ typedef struct RelationData
MemoryContext rd_rulescxt; /* private memory cxt for rd_rules, if any */ MemoryContext rd_rulescxt; /* private memory cxt for rd_rules, if any */
TriggerDesc *trigdesc; /* Trigger info, or NULL if rel has none */ 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. * 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 * Note that you can NOT look into rd_rel for this data. NULL means "use
......
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;
...@@ -83,7 +83,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi ...@@ -83,7 +83,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
# ---------- # ----------
# Another group of parallel tests # 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 # Another group of parallel tests
......
...@@ -98,6 +98,7 @@ test: security_label ...@@ -98,6 +98,7 @@ test: security_label
test: collate test: collate
test: matview test: matview
test: lock test: lock
test: replica_identity
test: alter_generic test: alter_generic
test: misc test: misc
test: psql test: psql
......
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;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment