From 688b801b8799ecd9827ee203bfb690536ac84ff4 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Mon, 4 Feb 2019 13:43:58 -0300 Subject: [PATCH v2 2/2] Propagate replica identity to partitions --- src/backend/commands/tablecmds.c | 108 +++++++++++++++-- src/bin/psql/describe.c | 3 +- src/test/regress/expected/replica_identity.out | 160 +++++++++++++++++++++++++ src/test/regress/sql/replica_identity.sql | 43 +++++++ 4 files changed, 304 insertions(+), 10 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 877bac506f..22cec85ab0 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -300,7 +300,7 @@ static void truncate_check_activity(Relation rel); static void RangeVarCallbackForTruncate(const RangeVar *relation, Oid relId, Oid oldRelId, void *arg); static List *MergeAttributes(List *schema, List *supers, char relpersistence, - bool is_partition, List **supconstr); + bool is_partition, List **supconstr, char *ri_type); static bool MergeCheckConstraint(List *constraints, char *name, Node *expr); static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel); static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel); @@ -485,6 +485,7 @@ static void AttachPartitionEnsureIndexes(Relation rel, Relation attachrel); static void QueuePartitionConstraintValidation(List **wqueue, Relation scanrel, List *partConstraint, bool validate_default); +static void MatchReplicaIdentity(Relation tgtrel, Relation srcrel); static void CloneRowTriggersToPartition(Relation parent, Relation partition); static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name); static ObjectAddress ATExecAttachPartitionIdx(List **wqueue, Relation rel, @@ -527,6 +528,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, TupleDesc descriptor; List *inheritOids; List *old_constraints; + char ri_type; List *rawDefaults; List *cookedDefaults; Datum reloptions; @@ -708,7 +710,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, MergeAttributes(stmt->tableElts, inheritOids, stmt->relation->relpersistence, stmt->partbound != NULL, - &old_constraints); + &old_constraints, &ri_type); /* * Create a tuple descriptor from the relation schema. Note that this @@ -1014,6 +1016,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, */ CloneForeignKeyConstraints(parentId, relationId, NULL); + /* Propagate REPLICA IDENTITY information too */ + if (ri_type != REPLICA_IDENTITY_DEFAULT) + MatchReplicaIdentity(rel, parent); + table_close(parent, NoLock); } @@ -1873,6 +1879,8 @@ storage_name(char c) * Output arguments: * 'supconstr' receives a list of constraints belonging to the parents, * updated as necessary to be valid for the child. + * 'ri_type' receives the replica identity type of the last parent seen, + * or default if none. * * Return value: * Completed schema list. @@ -1914,11 +1922,15 @@ storage_name(char c) * (4) Otherwise the inherited default is used. * Rule (3) is new in Postgres 7.1; in earlier releases you got a * rather arbitrary choice of which parent default to use. + * + * It only makes sense to use the returned 'ri_type' when there's a single + * parent, such as in declarative partitioning. *---------- */ static List * MergeAttributes(List *schema, List *supers, char relpersistence, - bool is_partition, List **supconstr) + bool is_partition, List **supconstr, + char *ri_type) { ListCell *entry; List *inhSchema = NIL; @@ -2015,6 +2027,11 @@ MergeAttributes(List *schema, List *supers, char relpersistence, } /* + * Initialize replica identity to default; parents may change it later + */ + *ri_type = REPLICA_IDENTITY_DEFAULT; + + /* * Scan the parents left-to-right, and merge their attributes to form a * list of inherited attributes (inhSchema). Also check to see if we need * to inherit an OID column. @@ -2095,6 +2112,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence, ? "cannot inherit from temporary relation of another session" : "cannot create as partition of temporary relation of another session"))); + /* Indicate replica identity back to caller */ + *ri_type = relation->rd_rel->relreplident; + /* * We should have an UNDER permission flag for this, but for now, * demand that creator of a child table own the parent. @@ -3935,7 +3955,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, case AT_ReplicaIdentity: /* REPLICA IDENTITY ... */ ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW); pass = AT_PASS_MISC; - /* This command never recurses */ + /* Recursion occurs during execution phase */ /* No command-specific prep needed */ break; case AT_EnableTrig: /* ENABLE TRIGGER variants */ @@ -12756,7 +12776,7 @@ ATExecDropOf(Relation rel, LOCKMODE lockmode) */ static void relation_mark_replica_identity(Relation rel, char ri_type, Oid indexOid, - bool is_internal) + bool is_internal, LOCKMODE lockmode) { Relation pg_index; Relation pg_class; @@ -12847,6 +12867,42 @@ relation_mark_replica_identity(Relation rel, char ri_type, Oid indexOid, } table_close(pg_index, RowExclusiveLock); + + /* + * If there are any partitions, handle them too. + */ + if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + PartitionDesc pd = RelationGetPartitionDesc(rel); + + for (int i = 0; i < pd->nparts; i++) + { + Relation part = table_open(pd->oids[i], lockmode); + Oid idxOid; + + if (ri_type == REPLICA_IDENTITY_INDEX) + { + idxOid = index_get_partition(part, indexOid); + if (!OidIsValid(idxOid)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("replica index does not exist in partition \"%s\"", + RelationGetRelationName(part)))); + + LockRelationOid(idxOid, ShareLock); + } + else + idxOid = InvalidOid; + + idxOid = ri_type == REPLICA_IDENTITY_INDEX ? + index_get_partition(part, indexOid) : InvalidOid; + + relation_mark_replica_identity(part, ri_type, idxOid, true, + lockmode); + + table_close(part, NoLock); + } + } } /* @@ -12861,17 +12917,20 @@ ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode if (stmt->identity_type == REPLICA_IDENTITY_DEFAULT) { - relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true); + relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true, + lockmode); return; } else if (stmt->identity_type == REPLICA_IDENTITY_FULL) { - relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true); + relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true, + lockmode); return; } else if (stmt->identity_type == REPLICA_IDENTITY_NOTHING) { - relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true); + relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true, + lockmode); return; } else if (stmt->identity_type == REPLICA_IDENTITY_INDEX) @@ -12959,7 +13018,8 @@ ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode } /* This index is suitable for use as a replica identity. Mark it. */ - relation_mark_replica_identity(rel, stmt->identity_type, indexOid, true); + relation_mark_replica_identity(rel, stmt->identity_type, indexOid, true, + lockmode); index_close(indexRel, NoLock); } @@ -14664,6 +14724,10 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd) /* and triggers */ CloneRowTriggersToPartition(rel, attachrel); + /* Propagate REPLICA IDENTITY information */ + if (rel->rd_rel->relreplident != REPLICA_IDENTITY_DEFAULT) + MatchReplicaIdentity(attachrel, rel); + /* * Clone foreign key constraints, and setup for Phase 3 to verify them. */ @@ -14915,6 +14979,32 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel) } /* + * Set up partRel's (a partition) replica identity to match parentRel's (its + * parent). + */ +static void +MatchReplicaIdentity(Relation partRel, Relation srcrel) +{ + Oid ri_index; + + if (srcrel->rd_rel->relreplident == REPLICA_IDENTITY_INDEX) + { + ri_index = index_get_partition(partRel, + RelationGetReplicaIndex(srcrel)); + if (!OidIsValid(ri_index)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("replica index does not exist in partition \"%s\"", + RelationGetRelationName(partRel)))); + } + else + ri_index = InvalidOid; + + relation_mark_replica_identity(partRel, srcrel->rd_rel->relreplident, + ri_index, true, AccessExclusiveLock); +} + +/* * CloneRowTriggersToPartition * subroutine for ATExecAttachPartition/DefineRelation to create row * triggers on partitions diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 4da6719ce7..6145a000cb 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -3113,7 +3113,8 @@ describeOneTableDetails(const char *schemaname, if (verbose && (tableinfo.relkind == RELKIND_RELATION || - tableinfo.relkind == RELKIND_MATVIEW) && + tableinfo.relkind == RELKIND_MATVIEW || + tableinfo.relkind == RELKIND_PARTITIONED_TABLE) && /* * No need to display default values; we already display a REPLICA diff --git a/src/test/regress/expected/replica_identity.out b/src/test/regress/expected/replica_identity.out index 175ecd2879..d6014df840 100644 --- a/src/test/regress/expected/replica_identity.out +++ b/src/test/regress/expected/replica_identity.out @@ -181,3 +181,163 @@ SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; DROP TABLE test_replica_identity; DROP TABLE test_replica_identity_othertable; +---- +-- Make sure it propagates to partitions +---- +CREATE TABLE test_replica_identity_part (a int, b int) PARTITION BY RANGE (a); +CREATE TABLE test_replica_identity_part1 PARTITION OF test_replica_identity_part + FOR VALUES FROM (0) TO (1000) PARTITION BY RANGE (a); +CREATE TABLE test_replica_identity_part2 PARTITION OF test_replica_identity_part + FOR VALUES FROM (1000) TO (2000); +CREATE TABLE test_replica_identity_part11 PARTITION OF test_replica_identity_part1 + FOR VALUES FROM (1000) TO (1500); +ALTER TABLE test_replica_identity_part REPLICA IDENTITY FULL; +CREATE TABLE test_replica_identity_part3 PARTITION OF test_replica_identity_part + FOR VALUES FROM (2000) TO (3000); +CREATE TABLE test_replica_identity_part4 (LIKE test_replica_identity_part); +ALTER TABLE test_replica_identity_part ATTACH PARTITION test_replica_identity_part4 + FOR VALUES FROM (3000) TO (4000); +\d+ test_replica_identity_part2 + Table "public.test_replica_identity_part2" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | | | plain | | + b | integer | | | | plain | | +Partition of: test_replica_identity_part FOR VALUES FROM (1000) TO (2000) +Partition constraint: ((a IS NOT NULL) AND (a >= 1000) AND (a < 2000)) +Replica Identity: FULL + +\d+ test_replica_identity_part11 + Table "public.test_replica_identity_part11" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | | | plain | | + b | integer | | | | plain | | +Partition of: test_replica_identity_part1 FOR VALUES FROM (1000) TO (1500) +Partition constraint: ((a IS NOT NULL) AND (a >= 0) AND (a < 1000) AND (a IS NOT NULL) AND (a >= 1000) AND (a < 1500)) +Replica Identity: FULL + +\d+ test_replica_identity_part + Partitioned table "public.test_replica_identity_part" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | | | plain | | + b | integer | | | | plain | | +Partition key: RANGE (a) +Partitions: test_replica_identity_part1 FOR VALUES FROM (0) TO (1000), PARTITIONED, + test_replica_identity_part2 FOR VALUES FROM (1000) TO (2000), + test_replica_identity_part3 FOR VALUES FROM (2000) TO (3000), + test_replica_identity_part4 FOR VALUES FROM (3000) TO (4000) +Replica Identity: FULL + +\d+ test_replica_identity_part3 + Table "public.test_replica_identity_part3" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | | | plain | | + b | integer | | | | plain | | +Partition of: test_replica_identity_part FOR VALUES FROM (2000) TO (3000) +Partition constraint: ((a IS NOT NULL) AND (a >= 2000) AND (a < 3000)) +Replica Identity: FULL + +\d+ test_replica_identity_part4 + Table "public.test_replica_identity_part4" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | | | plain | | + b | integer | | | | plain | | +Partition of: test_replica_identity_part FOR VALUES FROM (3000) TO (4000) +Partition constraint: ((a IS NOT NULL) AND (a >= 3000) AND (a < 4000)) +Replica Identity: FULL + +ALTER TABLE test_replica_identity_part ALTER a SET NOT NULL; +CREATE UNIQUE INDEX trip_b_idx ON test_replica_identity_part (a); +ALTER TABLE test_replica_identity_part REPLICA IDENTITY USING INDEX trip_b_idx; +\d+ test_replica_identity_part2 + Table "public.test_replica_identity_part2" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | not null | | plain | | + b | integer | | | | plain | | +Partition of: test_replica_identity_part FOR VALUES FROM (1000) TO (2000) +Partition constraint: ((a IS NOT NULL) AND (a >= 1000) AND (a < 2000)) +Indexes: + "test_replica_identity_part2_a_idx" UNIQUE, btree (a) REPLICA IDENTITY + +\d+ test_replica_identity_part11 + Table "public.test_replica_identity_part11" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | not null | | plain | | + b | integer | | | | plain | | +Partition of: test_replica_identity_part1 FOR VALUES FROM (1000) TO (1500) +Partition constraint: ((a IS NOT NULL) AND (a >= 0) AND (a < 1000) AND (a IS NOT NULL) AND (a >= 1000) AND (a < 1500)) +Indexes: + "test_replica_identity_part11_a_idx" UNIQUE, btree (a) REPLICA IDENTITY + +\d+ test_replica_identity_part + Partitioned table "public.test_replica_identity_part" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | not null | | plain | | + b | integer | | | | plain | | +Partition key: RANGE (a) +Indexes: + "trip_b_idx" UNIQUE, btree (a) REPLICA IDENTITY +Partitions: test_replica_identity_part1 FOR VALUES FROM (0) TO (1000), PARTITIONED, + test_replica_identity_part2 FOR VALUES FROM (1000) TO (2000), + test_replica_identity_part3 FOR VALUES FROM (2000) TO (3000), + test_replica_identity_part4 FOR VALUES FROM (3000) TO (4000) + +\d+ test_replica_identity_part3 + Table "public.test_replica_identity_part3" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | not null | | plain | | + b | integer | | | | plain | | +Partition of: test_replica_identity_part FOR VALUES FROM (2000) TO (3000) +Partition constraint: ((a IS NOT NULL) AND (a >= 2000) AND (a < 3000)) +Indexes: + "test_replica_identity_part3_a_idx" UNIQUE, btree (a) REPLICA IDENTITY + +\d+ test_replica_identity_part4 + Table "public.test_replica_identity_part4" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | not null | | plain | | + b | integer | | | | plain | | +Partition of: test_replica_identity_part FOR VALUES FROM (3000) TO (4000) +Partition constraint: ((a IS NOT NULL) AND (a >= 3000) AND (a < 4000)) +Indexes: + "test_replica_identity_part4_a_idx" UNIQUE, btree (a) REPLICA IDENTITY + +---- +-- Check behavior with inherited tables +---- +CREATE TABLE test_replica_identity_inh (a int); +CREATE TABLE test_replica_identity_cld () INHERITS (test_replica_identity_inh); +ALTER TABLE test_replica_identity_inh REPLICA IDENTITY FULL; +CREATE TABLE test_replica_identity_cld2 () INHERITS (test_replica_identity_inh); +\d+ test_replica_identity_inh + Table "public.test_replica_identity_inh" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | | | plain | | +Child tables: test_replica_identity_cld, + test_replica_identity_cld2 +Replica Identity: FULL + +\d+ test_replica_identity_cld + Table "public.test_replica_identity_cld" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | | | plain | | +Inherits: test_replica_identity_inh + +\d+ test_replica_identity_cld2 + Table "public.test_replica_identity_cld2" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | | | plain | | +Inherits: test_replica_identity_inh + diff --git a/src/test/regress/sql/replica_identity.sql b/src/test/regress/sql/replica_identity.sql index b08a3623b8..9e309796f2 100644 --- a/src/test/regress/sql/replica_identity.sql +++ b/src/test/regress/sql/replica_identity.sql @@ -77,3 +77,46 @@ SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; DROP TABLE test_replica_identity; DROP TABLE test_replica_identity_othertable; + +---- +-- Make sure it propagates to partitions +---- +CREATE TABLE test_replica_identity_part (a int, b int) PARTITION BY RANGE (a); +CREATE TABLE test_replica_identity_part1 PARTITION OF test_replica_identity_part + FOR VALUES FROM (0) TO (1000) PARTITION BY RANGE (a); +CREATE TABLE test_replica_identity_part2 PARTITION OF test_replica_identity_part + FOR VALUES FROM (1000) TO (2000); +CREATE TABLE test_replica_identity_part11 PARTITION OF test_replica_identity_part1 + FOR VALUES FROM (1000) TO (1500); +ALTER TABLE test_replica_identity_part REPLICA IDENTITY FULL; +CREATE TABLE test_replica_identity_part3 PARTITION OF test_replica_identity_part + FOR VALUES FROM (2000) TO (3000); +CREATE TABLE test_replica_identity_part4 (LIKE test_replica_identity_part); +ALTER TABLE test_replica_identity_part ATTACH PARTITION test_replica_identity_part4 + FOR VALUES FROM (3000) TO (4000); +\d+ test_replica_identity_part2 +\d+ test_replica_identity_part11 +\d+ test_replica_identity_part +\d+ test_replica_identity_part3 +\d+ test_replica_identity_part4 + +ALTER TABLE test_replica_identity_part ALTER a SET NOT NULL; +CREATE UNIQUE INDEX trip_b_idx ON test_replica_identity_part (a); +ALTER TABLE test_replica_identity_part REPLICA IDENTITY USING INDEX trip_b_idx; +\d+ test_replica_identity_part2 +\d+ test_replica_identity_part11 +\d+ test_replica_identity_part +\d+ test_replica_identity_part3 +\d+ test_replica_identity_part4 + + +---- +-- Check behavior with inherited tables +---- +CREATE TABLE test_replica_identity_inh (a int); +CREATE TABLE test_replica_identity_cld () INHERITS (test_replica_identity_inh); +ALTER TABLE test_replica_identity_inh REPLICA IDENTITY FULL; +CREATE TABLE test_replica_identity_cld2 () INHERITS (test_replica_identity_inh); +\d+ test_replica_identity_inh +\d+ test_replica_identity_cld +\d+ test_replica_identity_cld2 -- 2.11.0