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