From b7bddbe815109f7fda24d8d1ca3fc2b097a55424 Mon Sep 17 00:00:00 2001 From: Shveta Malik Date: Tue, 9 Jul 2024 14:37:08 +0530 Subject: [PATCH v5 5/5] Configure table level conflict resolvers This patch provides support for configuring table level conflict resolvers using ALTER TABLE cmd. Syntax to SET resolvers: ALTER TABLE SET CONFLICT RESOLVER on , SET CONFLICT RESOLVER on , ...; A new catalog table pg_conflict_rel has been created to store table-level conflict_type and conflict_resolver configurations given by above DDL command. Syntax to RESET resolvers: ALTER TABLE RESET CONFLICT RESOLVER on , RESET CONFLICT RESOLVER on , ...; Above RESET command will remove entry for that particular conflict_type for the given table from pg_conflict_rel catalog table. --- src/backend/catalog/dependency.c | 6 + src/backend/catalog/objectaddress.c | 30 ++ src/backend/commands/tablecmds.c | 70 ++++ src/backend/parser/gram.y | 30 ++ src/backend/replication/logical/conflict.c | 330 +++++++++++++++++- src/include/catalog/Makefile | 4 +- src/include/catalog/meson.build | 1 + src/include/catalog/pg_conflict.h | 2 +- src/include/catalog/pg_conflict_rel.h | 58 +++ src/include/nodes/parsenodes.h | 2 + src/include/replication/conflict.h | 13 + .../regress/expected/conflict_resolver.out | 245 ++++++++++++- src/test/regress/expected/oidjoins.out | 1 + src/test/regress/sql/conflict_resolver.sql | 161 ++++++++- 14 files changed, 929 insertions(+), 24 deletions(-) create mode 100644 src/include/catalog/pg_conflict_rel.h diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 0489cbabcb..9dd78d9094 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -31,6 +31,7 @@ #include "catalog/pg_auth_members.h" #include "catalog/pg_cast.h" #include "catalog/pg_collation.h" +#include "catalog/pg_conflict_rel.h" #include "catalog/pg_constraint.h" #include "catalog/pg_conversion.h" #include "catalog/pg_database.h" @@ -79,6 +80,7 @@ #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "parser/parsetree.h" +#include "replication/conflict.h" #include "rewrite/rewriteRemove.h" #include "storage/lmgr.h" #include "utils/fmgroids.h" @@ -1444,6 +1446,10 @@ doDeletion(const ObjectAddress *object, int flags) RemovePublicationById(object->objectId); break; + case ConflictRelRelationId: + RemoveTableConflictById(object->objectId); + break; + case CastRelationId: case CollationRelationId: case ConversionRelationId: diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index 2983b9180f..1ba0f74a2d 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -30,6 +30,7 @@ #include "catalog/pg_cast.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_conflict_rel.h" #include "catalog/pg_conversion.h" #include "catalog/pg_database.h" #include "catalog/pg_default_acl.h" @@ -3005,6 +3006,35 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok) break; } + case ConflictRelRelationId: + { + HeapTuple confTup; + Datum typeDatum; + Datum resDatum; + char *contype; + char *conres; + + confTup = SearchSysCache1(CONFLICTRELOID, + ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(confTup)) + { + if (!missing_ok) + elog(ERROR, "cache lookup failed for table conflict %u", + object->objectId); + break; + } + + typeDatum = SysCacheGetAttrNotNull(CONFLICTRELOID, confTup, + Anum_pg_conflict_rel_confrtype); + resDatum = SysCacheGetAttrNotNull(CONFLICTRELOID, confTup, + Anum_pg_conflict_rel_confrres); + contype = TextDatumGetCString(typeDatum); + conres = TextDatumGetCString(resDatum); + ReleaseSysCache(confTup); + appendStringInfo(&buffer, _("conflict_resolver %s on conflict_type %s"), + conres, contype); + break; + } case ConstraintRelationId: { HeapTuple conTup; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index dbfe0d6b1c..2d75297340 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -83,6 +83,7 @@ #include "partitioning/partbounds.h" #include "partitioning/partdesc.h" #include "pgstat.h" +#include "replication/conflict.h" #include "rewrite/rewriteDefine.h" #include "rewrite/rewriteHandler.h" #include "rewrite/rewriteManip.h" @@ -662,6 +663,11 @@ static void ATExecSplitPartition(List **wqueue, AlteredTableInfo *tab, AlterTableUtilityContext *context); static void ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel, PartitionCmd *cmd, AlterTableUtilityContext *context); +static void ATExecSetConflictResolver(Relation rel, ConflictResolverStmt *stmt, + bool recurse, bool recursing, LOCKMODE lockmode); +static void + ATExecResetConflictResolver(Relation rel, ConflictResolverStmt *stmt, + bool recurse, bool recursing, LOCKMODE lockmode); /* ---------------------------------------------------------------- * DefineRelation @@ -1245,6 +1251,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, */ CloneForeignKeyConstraints(NULL, parent, rel); + /* Inherit conflict resolvers configuration from parent. */ + InheritTableConflictResolvers(rel, parent); + table_close(parent, NoLock); } @@ -4543,6 +4552,8 @@ AlterTableGetLockLevel(List *cmds) case AT_SetExpression: case AT_DropExpression: case AT_SetCompression: + case AT_SetConflictResolver: + case AT_ResetConflictResolver: cmd_lockmode = AccessExclusiveLock; break; @@ -5116,6 +5127,15 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* No command-specific prep needed */ pass = AT_PASS_MISC; break; + case AT_ResetConflictResolver: /* RESET CONFLICT RESOLVER */ + case AT_SetConflictResolver: /* SET CONFLICT RESOLVER */ + ATSimplePermissions(cmd->subtype, rel, ATT_TABLE); + /* Recursion occurs during execution phase */ + /* No command-specific prep needed except saving recurse flag */ + if (recurse) + cmd->recurse = true; + pass = AT_PASS_MISC; + break; default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype); @@ -5528,6 +5548,14 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, ATExecMergePartitions(wqueue, tab, rel, (PartitionCmd *) cmd->def, context); break; + case AT_SetConflictResolver: + ATExecSetConflictResolver(rel, (ConflictResolverStmt *) cmd->def, + cmd->recurse, false, lockmode); + break; + case AT_ResetConflictResolver: + ATExecResetConflictResolver(rel, (ConflictResolverStmt *) cmd->def, + cmd->recurse, false, lockmode); + break; default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype); @@ -6528,6 +6556,10 @@ alter_table_type_to_string(AlterTableType cmdtype) return "ALTER COLUMN ... DROP IDENTITY"; case AT_ReAddStatistics: return NULL; /* not real grammar */ + case AT_SetConflictResolver: + return "SET CONFLICT RESOLVER"; + case AT_ResetConflictResolver: + return "RESET CONFLICT RESOLVER"; } return NULL; @@ -15650,6 +15682,13 @@ CreateInheritance(Relation child_rel, Relation parent_rel, bool ispartition) /* Match up the constraints and bump coninhcount as needed */ MergeConstraintsIntoExisting(child_rel, parent_rel); + /* + * Inherit resolvers configuration from parent if not explicitly set for + * child partition. + */ + if (ispartition) + InheritTableConflictResolvers(child_rel, parent_rel); + /* * OK, it looks valid. Make the catalog entries that show inheritance. */ @@ -16238,6 +16277,10 @@ RemoveInheritance(Relation child_rel, Relation parent_rel, bool expect_detached) systable_endscan(scan); table_close(catalogRelation, RowExclusiveLock); + /* Find inherited conflict resolvers and disinherit them */ + if (is_partitioning) + ResetResolversInheritance(child_rel); + drop_parent_dependency(RelationGetRelid(child_rel), RelationRelationId, RelationGetRelid(parent_rel), @@ -20766,3 +20809,30 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel, /* Keep the lock until commit. */ table_close(newPartRel, NoLock); } + +/* + * ALTER TABLE SET CONFLICT RESOLVER ... + */ +static void +ATExecSetConflictResolver(Relation rel, ConflictResolverStmt *stmt, + bool recurse, bool recursing, LOCKMODE lockmode) +{ + ValidateConflictTypeAndResolver(stmt->conflict_type, stmt->conflict_resolver, false); + + SetTableConflictResolver(NULL, rel, stmt->conflict_type, + stmt->conflict_resolver, + recurse, recursing, lockmode); +} + +/* + * ALTER TABLE RESET CONFLICT RESOLVER ... + */ +static void +ATExecResetConflictResolver(Relation rel, ConflictResolverStmt *stmt, + bool recurse, bool recursing, LOCKMODE lockmode) +{ + ValidateConflictTypeAndResolver(stmt->conflict_type, stmt->conflict_resolver, true); + + ResetTableConflictResolver(rel, stmt->conflict_type, + recurse, recursing, lockmode); +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 42726fe3a6..628b9d10e2 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -3015,6 +3015,36 @@ alter_table_cmd: n->subtype = AT_GenericOptions; n->def = (Node *) $1; + $$ = (Node *) n; + } + /* + * ALTER TABLE SET CONFLICT RESOLVER + * on + */ + | SET CONFLICT RESOLVER conflict_resolver ON conflict_type + { + AlterTableCmd *n = makeNode(AlterTableCmd); + ConflictResolverStmt *c = makeNode(ConflictResolverStmt); + + c->conflict_resolver = $4; + c->conflict_type = $6; + + n->subtype = AT_SetConflictResolver; + n->def = (Node *) c; + + $$ = (Node *) n; + } + /* ALTER TABLE RESET CONFLICT RESOLVER on */ + | RESET CONFLICT RESOLVER ON conflict_type + { + AlterTableCmd *n = makeNode(AlterTableCmd); + ConflictResolverStmt *c = makeNode(ConflictResolverStmt); + + c->conflict_type = $5; + + n->subtype = AT_ResetConflictResolver; + n->def = (Node *) c; + $$ = (Node *) n; } ; diff --git a/src/backend/replication/logical/conflict.c b/src/backend/replication/logical/conflict.c index c330b21217..37615b6e85 100644 --- a/src/backend/replication/logical/conflict.c +++ b/src/backend/replication/logical/conflict.c @@ -19,8 +19,11 @@ #include "access/htup_details.h" #include "access/skey.h" #include "access/table.h" +#include "catalog/dependency.h" #include "catalog/indexing.h" #include "catalog/pg_conflict.h" +#include "catalog/pg_conflict_rel.h" +#include "catalog/pg_inherits.h" #include "executor/executor.h" #include "replication/conflict.h" #include "replication/logicalproto.h" @@ -28,8 +31,10 @@ #include "replication/origin.h" #include "utils/builtins.h" #include "utils/fmgroids.h" +#include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" #include "utils/syscache.h" @@ -294,9 +299,9 @@ build_index_value_desc(Oid indexoid, TupleTableSlot *conflictslot) * * Return ConflictType enum value corresponding to given conflict_type. */ -static ConflictType -validate_conflict_type_and_resolver(char *conflict_type, char *conflict_resolver, - bool isReset) +ConflictType +ValidateConflictTypeAndResolver(char *conflict_type, char *conflict_resolver, + bool isReset) { ConflictType type; ConflictResolver resolver; @@ -495,10 +500,9 @@ ExecConflictResolverStmt(ConflictResolverStmt *stmt) HeapTuple newtup = NULL; ConflictType type; - type = validate_conflict_type_and_resolver(stmt->conflict_type, - stmt->conflict_resolver, - stmt->isReset); - + type = ValidateConflictTypeAndResolver(stmt->conflict_type, + stmt->conflict_resolver, + stmt->isReset); /* Prepare to update a tuple in pg_conflict system catalog */ memset(values, 0, sizeof(values)); @@ -526,7 +530,7 @@ ExecConflictResolverStmt(ConflictResolverStmt *stmt) BTEqualStrategyNumber, F_TEXTEQ, values[Anum_pg_conflict_conftype - 1]); - pg_conflict = table_open(ConflictResRelationId, RowExclusiveLock); + pg_conflict = table_open(ConflictRelationId, RowExclusiveLock); scan = systable_beginscan(pg_conflict, ConflictTypeIndexId, true, NULL, 1, keys); @@ -623,3 +627,313 @@ GetConflictResolver(TupleTableSlot *localslot, Relation localrel, return resolver; } + +/* + * Update the table level conflict resolver for a conflict_type in + * pg_conflict_rel system catalog. + */ +void +SetTableConflictResolver(Relation pg_conf_rel, Relation rel, char *conflict_type, char *conflict_resolver, + bool recurse, bool recursing, LOCKMODE lockmode) +{ + Relation pg_conflict_rel; + Datum values[Natts_pg_conflict_rel]; + bool nulls[Natts_pg_conflict_rel]; + bool replaces[Natts_pg_conflict_rel]; + HeapTuple oldtup; + HeapTuple newtup = NULL; + Oid relid = rel->rd_id; + + /* Prepare to update a tuple. */ + memset(nulls, false, sizeof(nulls)); + memset(replaces, false, sizeof(replaces)); + + if (!pg_conf_rel) + pg_conflict_rel = table_open(ConflictRelRelationId, RowExclusiveLock); + else + pg_conflict_rel = pg_conf_rel; + + values[Anum_pg_conflict_rel_confrrelid - 1] = ObjectIdGetDatum(relid); + values[Anum_pg_conflict_rel_confrtype - 1] = CStringGetTextDatum(conflict_type); + values[Anum_pg_conflict_rel_confrres - 1] = CStringGetTextDatum(conflict_resolver); + values[Anum_pg_conflict_rel_confrinherited - 1] = BoolGetDatum(recursing); + + oldtup = SearchSysCache2(CONFLICTRELTYPE, + values[Anum_pg_conflict_rel_confrrelid - 1], + values[Anum_pg_conflict_rel_confrtype - 1]); + if (HeapTupleIsValid(oldtup)) + { + Form_pg_conflict_rel confForm = (Form_pg_conflict_rel) GETSTRUCT(oldtup); + + /* + * Update resolver for recursing=false cases. + * + * recursing=true indicates it is a child parittion table and if it + * already has a resolver set then overwrite it only if it is an + * inherited resolver. We should not overwrite non-inherited resolvers + * for child partition tables during recursion. + */ + if (!recursing || (recursing && confForm->confrinherited)) + { + replaces[Anum_pg_conflict_rel_confrres - 1] = true; + replaces[Anum_pg_conflict_rel_confrinherited - 1] = !recursing; + + newtup = heap_modify_tuple(oldtup, RelationGetDescr(pg_conflict_rel), + values, nulls, replaces); + CatalogTupleUpdate(pg_conflict_rel, &oldtup->t_self, newtup); + } + else + { + /* + * If we did not update resolver for this child table, do not + * update for child's child tables as well. + */ + recurse = false; + } + + ReleaseSysCache(oldtup); + } + /* If we didn't find an old tuple, insert a new one */ + else + { + ObjectAddress myself, + referenced; + Oid conflict_oid; + + conflict_oid = GetNewOidWithIndex(pg_conflict_rel, ConflictRelOidIndexId, + Anum_pg_conflict_rel_oid); + values[Anum_pg_conflict_rel_oid - 1] = ObjectIdGetDatum(conflict_oid); + + newtup = heap_form_tuple(RelationGetDescr(pg_conflict_rel), + values, nulls); + CatalogTupleInsert(pg_conflict_rel, newtup); + + /* Add dependency on the relation */ + ObjectAddressSet(myself, ConflictRelRelationId, conflict_oid); + ObjectAddressSet(referenced, RelationRelationId, relid); + recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); + } + + if (HeapTupleIsValid(newtup)) + heap_freetuple(newtup); + + if (!pg_conf_rel) + table_close(pg_conflict_rel, RowExclusiveLock); + + if (recurse && (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)) + { + List *children; + ListCell *lc; + + children = + find_inheritance_children(RelationGetRelid(rel), lockmode); + + foreach(lc, children) + { + Relation childrel; + + /* find_inheritance_children already got lock */ + childrel = table_open(lfirst_oid(lc), NoLock); + SetTableConflictResolver(NULL, childrel, conflict_type, conflict_resolver, recurse, true, lockmode); + table_close(childrel, NoLock); + } + } +} + +/* + * Reset table level conflict_type configuration. + * + * Removes table's resolver configuration for given conflict_type from + * pg_conflict_rel system catalog. + */ +void +ResetTableConflictResolver(Relation rel, char *conflict_type, + bool recurse, bool recursing, LOCKMODE lockmode) +{ + Oid relid = rel->rd_id; + HeapTuple tup; + ObjectAddress confobj; + Form_pg_conflict_rel confForm; + + tup = SearchSysCache2(CONFLICTRELTYPE, + ObjectIdGetDatum(relid), + CStringGetTextDatum(conflict_type)); + + /* Nothing to reset */ + if (!HeapTupleIsValid(tup)) + return; + + confForm = (Form_pg_conflict_rel) GETSTRUCT(tup); + + if (confForm->confrinherited && !recursing) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot reset inherited resolver for conflict type \"%s\" of relation \"%s\"", + conflict_type, RelationGetRelationName(rel)))); + + /* + * While resetting resolver on parent table, reset resolver for child + * partition table only if it is inherited one. + * + * OTOH, if user has invoked RESET directly on child partition table, + * ensure we reset only non-inherited resolvers. Inherited resolvers can + * not be reset alone on child table, RESET has to come through parent + * table. + */ + if ((recursing && confForm->confrinherited) || + (!recursing && !confForm->confrinherited)) + { + ObjectAddressSet(confobj, ConflictRelRelationId, confForm->oid); + performDeletion(&confobj, DROP_CASCADE, 0); + } + else + { + /* + * If we did not reset resolver for this child table, do not reset for + * child's child tables as well. + */ + recurse = false; + } + + ReleaseSysCache(tup); + + if (recurse && (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)) + { + List *children; + ListCell *lc; + + children = + find_inheritance_children(RelationGetRelid(rel), lockmode); + + foreach(lc, children) + { + Relation childrel; + + /* find_inheritance_children already got lock */ + childrel = table_open(lfirst_oid(lc), NoLock); + ResetTableConflictResolver(childrel, conflict_type, recurse, true, lockmode); + table_close(childrel, NoLock); + } + } +} + +/* + * Inherit conflict resolvers from parent paritioned table. + * + * If child partition table has resolvers set already for some conflict + * types, do not overwrite those, inherit rest from parent. + * + * Used during attach partition operation. + */ +void +InheritTableConflictResolvers(Relation child_rel, Relation parent_rel) +{ + Relation pg_conflict_rel; + SysScanDesc parent_scan; + ScanKeyData parent_key; + HeapTuple parent_tuple; + Oid parent_relid = RelationGetRelid(parent_rel); + + Assert(parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); + + pg_conflict_rel = table_open(ConflictRelRelationId, RowExclusiveLock); + + ScanKeyInit(&parent_key, + Anum_pg_conflict_rel_confrrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(parent_relid)); + + parent_scan = systable_beginscan(pg_conflict_rel, InvalidOid, false, NULL, 1, &parent_key); + + while (HeapTupleIsValid(parent_tuple = systable_getnext(parent_scan))) + { + Datum typeDatum; + Datum resDatum; + char *parent_conftype; + char *parent_confres; + + typeDatum = SysCacheGetAttrNotNull(CONFLICTRELOID, parent_tuple, + Anum_pg_conflict_rel_confrtype); + parent_conftype = TextDatumGetCString(typeDatum); + + resDatum = SysCacheGetAttrNotNull(CONFLICTRELOID, parent_tuple, + Anum_pg_conflict_rel_confrres); + parent_confres = TextDatumGetCString(resDatum); + + SetTableConflictResolver(pg_conflict_rel, child_rel, parent_conftype, parent_confres, true, true, AccessExclusiveLock); + } + + systable_endscan(parent_scan); + table_close(pg_conflict_rel, RowExclusiveLock); +} + +/* + * Reset inheritance of conflict resolvers. + * + * Used during detach partition operation. Detach partition + * will not remove inherited resolvers but will mark them + * as non inherited. + */ +void +ResetResolversInheritance(Relation rel) +{ + Relation pg_conflict_rel; + SysScanDesc scan; + ScanKeyData key; + HeapTuple tuple; + Oid relid = RelationGetRelid(rel); + + Assert(rel->rd_rel->relispartition); + + pg_conflict_rel = table_open(ConflictRelRelationId, RowExclusiveLock); + + ScanKeyInit(&key, + Anum_pg_conflict_rel_confrrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relid)); + + scan = systable_beginscan(pg_conflict_rel, InvalidOid, false, NULL, 1, &key); + + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + Form_pg_conflict_rel conf = + (Form_pg_conflict_rel) GETSTRUCT(tuple); + + if (conf->confrinherited) + { + HeapTuple copy_tuple = heap_copytuple(tuple); + Form_pg_conflict_rel copy_conf = + (Form_pg_conflict_rel) GETSTRUCT(copy_tuple); + + copy_conf->confrinherited = false; + CatalogTupleUpdate(pg_conflict_rel, ©_tuple->t_self, copy_tuple); + heap_freetuple(copy_tuple); + + } + } + + systable_endscan(scan); + table_close(pg_conflict_rel, RowExclusiveLock); +} + +/* + * Remove the conflict resolver configuration by table conflict oid. + */ +void +RemoveTableConflictById(Oid confid) +{ + Relation rel; + HeapTuple tup; + + rel = table_open(ConflictRelRelationId, RowExclusiveLock); + + tup = SearchSysCache1(CONFLICTRELOID, ObjectIdGetDatum(confid)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for table conflict %u", confid); + + CatalogTupleDelete(rel, &tup->t_self); + + ReleaseSysCache(tup); + + table_close(rel, RowExclusiveLock); +} diff --git a/src/include/catalog/Makefile b/src/include/catalog/Makefile index 8619d73e5a..e192375fa0 100644 --- a/src/include/catalog/Makefile +++ b/src/include/catalog/Makefile @@ -82,7 +82,9 @@ CATALOG_HEADERS := \ pg_publication_rel.h \ pg_subscription.h \ pg_subscription_rel.h \ - pg_conflict.h + pg_conflict.h \ + pg_conflict_rel.h + GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) diff --git a/src/include/catalog/meson.build b/src/include/catalog/meson.build index 4d6732a303..28924f5df8 100644 --- a/src/include/catalog/meson.build +++ b/src/include/catalog/meson.build @@ -70,6 +70,7 @@ catalog_headers = [ 'pg_subscription.h', 'pg_subscription_rel.h', 'pg_conflict.h', + 'pg_conflict_rel.h', ] # The .dat files we need can just be listed alphabetically. diff --git a/src/include/catalog/pg_conflict.h b/src/include/catalog/pg_conflict.h index e3fe3e6d30..c080a0d0be 100644 --- a/src/include/catalog/pg_conflict.h +++ b/src/include/catalog/pg_conflict.h @@ -26,7 +26,7 @@ * typedef struct FormData_pg_conflict * ---------------- */ -CATALOG(pg_conflict,8688,ConflictResRelationId) +CATALOG(pg_conflict,8688,ConflictRelationId) { #ifdef CATALOG_VARLEN /* variable-length fields start here */ text conftype BKI_FORCE_NOT_NULL; /* conflict type */ diff --git a/src/include/catalog/pg_conflict_rel.h b/src/include/catalog/pg_conflict_rel.h new file mode 100644 index 0000000000..636398baf1 --- /dev/null +++ b/src/include/catalog/pg_conflict_rel.h @@ -0,0 +1,58 @@ +/* ------------------------------------------------------------------------- + * + * pg_conflict_rel.h + * definition of the "table level conflict detection" system + * catalog (pg_conflict_rel) + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_conflict_rel.h + * + * NOTES + * The Catalog.pm module reads this file and derives schema + * information. + * + * ------------------------------------------------------------------------- + */ +#ifndef PG_CONFLICT_REL_H +#define PG_CONFLICT_REL_H + +#include "catalog/genbki.h" +#include "catalog/pg_conflict_rel_d.h" + +/* ---------------- + * pg_conflict_rel definition. cpp turns this into + * typedef struct FormData_pg_conflict_rel + * ---------------- + */ +CATALOG(pg_conflict_rel,8881,ConflictRelRelationId) +{ + Oid oid; /* OID of the object itself */ + Oid confrrelid BKI_LOOKUP(pg_class); /* Oid of the relation + * having resolver */ + bool confrinherited; /* Is this an inherited configuration */ + +#ifdef CATALOG_VARLEN /* variable-length fields start here */ + text confrtype BKI_FORCE_NOT_NULL; /* conflict type */ + text confrres BKI_FORCE_NOT_NULL; /* conflict resolver */ +#endif +} FormData_pg_conflict_rel; + +/* ---------------- + * Form_pg_conflict_rel corresponds to a pointer to a row with + * the format of pg_conflict_rel relation. + * ---------------- + */ +typedef FormData_pg_conflict_rel * Form_pg_conflict_rel; + +DECLARE_TOAST(pg_conflict_rel, 8882, 8883); + +DECLARE_UNIQUE_INDEX_PKEY(pg_conflict_rel_oid_index, 8884, ConflictRelOidIndexId, pg_conflict_rel, btree(oid oid_ops)); + +DECLARE_UNIQUE_INDEX(pg_conflict_rel_type_index, 8885, ConflictRelTypeIndexId, pg_conflict_rel, btree(confrrelid oid_ops, confrtype text_ops)); + +MAKE_SYSCACHE(CONFLICTRELOID, pg_conflict_rel_oid_index, 256); +MAKE_SYSCACHE(CONFLICTRELTYPE, pg_conflict_rel_type_index, 256); + +#endif /* PG_CONFLICT_REL_H */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 1d41169d5e..7677cf023b 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2419,6 +2419,8 @@ typedef enum AlterTableType AT_SetIdentity, /* SET identity column options */ AT_DropIdentity, /* DROP IDENTITY */ AT_ReAddStatistics, /* internal to commands/tablecmds.c */ + AT_SetConflictResolver, /* SET CONFLICT RESOLVER */ + AT_ResetConflictResolver, /* RESET CONFLICT RESOLVER */ } AlterTableType; typedef struct ReplicaIdentityStmt diff --git a/src/include/replication/conflict.h b/src/include/replication/conflict.h index d38a22ddde..3ba83a25cd 100644 --- a/src/include/replication/conflict.h +++ b/src/include/replication/conflict.h @@ -96,4 +96,17 @@ extern ConflictResolver GetConflictResolver(TupleTableSlot *localslot, TimestampTz committs); extern bool CanCreateFullTuple(Relation localrel, LogicalRepTupleData *newtup); + +extern ConflictType ValidateConflictTypeAndResolver(char *conflict_type, + char *conflict_resolver, + bool isReset); +extern void SetTableConflictResolver(Relation pg_conf_rel, Relation rel, char *conflict_type, + char *conflict_resolver, bool recurse, + bool recursing, LOCKMODE lockmode); +extern void ResetTableConflictResolver(Relation rel, char *conflict_type, + bool recurse, bool recursing, LOCKMODE lockmode); +extern void RemoveTableConflictById(Oid confid); +extern void InheritTableConflictResolvers(Relation child_rel, Relation parent_rel); +extern void ResetResolversInheritance(Relation rel); + #endif diff --git a/src/test/regress/expected/conflict_resolver.out b/src/test/regress/expected/conflict_resolver.out index c21486dbb4..802a7583d4 100644 --- a/src/test/regress/expected/conflict_resolver.out +++ b/src/test/regress/expected/conflict_resolver.out @@ -1,5 +1,8 @@ +-- +-- Test for configuration of global resolvers +-- --check default global resolvers in system catalog -select * from pg_conflict order by conftype; +SELECT * FROM pg_conflict order by conftype; conftype | confres ----------------+------------------ delete_differ | last_update_wins @@ -9,9 +12,7 @@ select * from pg_conflict order by conftype; update_missing | apply_or_skip (5 rows) --- -- Test of SET/RESET CONFLICT RESOLVER with invalid names --- SET CONFLICT RESOLVER 'keep_local' for 'aaaa'; -- fail ERROR: aaaa is not a valid conflict type SET CONFLICT RESOLVER 'bbbbb' for 'delete_missing'; -- fail @@ -20,16 +21,14 @@ SET CONFLICT RESOLVER 'remote_apply' for 'delete_missing'; -- fail ERROR: remote_apply is not a valid conflict resolver for conflict type delete_missing RESET CONFLICT RESOLVER for 'ct'; -- fail ERROR: ct is not a valid conflict type --- -- Test of SET/RESET CONFLICT RESOLVER with valid names --- SET CONFLICT RESOLVER 'error' for 'delete_missing'; SET CONFLICT RESOLVER 'keep_local' for 'insert_exists'; SET CONFLICT RESOLVER 'keep_local' for 'update_differ'; SET CONFLICT RESOLVER 'apply_or_error' for 'update_missing'; SET CONFLICT RESOLVER 'keep_local' for 'delete_differ'; --check new resolvers are saved -select * from pg_conflict order by conftype; +SELECT * FROM pg_conflict order by conftype; conftype | confres ----------------+---------------- delete_differ | keep_local @@ -42,7 +41,7 @@ select * from pg_conflict order by conftype; RESET CONFLICT RESOLVER for 'delete_missing'; RESET CONFLICT RESOLVER for 'insert_exists'; --check resolvers are reset to default for delete_missing and insert_exists -select * from pg_conflict order by conftype; +SELECT * FROM pg_conflict order by conftype; conftype | confres ----------------+------------------ delete_differ | keep_local @@ -52,3 +51,235 @@ select * from pg_conflict order by conftype; update_missing | apply_or_error (5 rows) +-- +-- Test for configuration of table level resolvers +-- +-- Test for ALTER TABLE..SET/RESET CONFLICT RESOLVER +CREATE TABLE ptntable ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +CREATE TABLE ptntable_1 PARTITION OF ptntable + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); +CREATE TABLE ptntable_2 PARTITION OF ptntable + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + PARTITION BY RANGE (logdate); +CREATE TABLE ptntable_2_1 PARTITION OF ptntable_2 + FOR VALUES FROM ('2006-03-20') TO ('2006-04-01'); +ALTER TABLE ptntable SET CONFLICT RESOLVER 'error' ON 'delete_missing', + SET CONFLICT RESOLVER 'keep_local' ON 'insert_exists'; +--Expect 8 entries. 2 for parent and 2 for each child partition (inherited +--resolvers) +SELECT relname, confrtype, confrres, confrinherited FROM pg_class pc, + (SELECT confrrelid, confrtype, confrres, confrinherited FROM pg_conflict_rel)conf + WHERE conf.confrrelid= pc.oid order by relname, confrtype; + relname | confrtype | confrres | confrinherited +--------------+----------------+------------+---------------- + ptntable | delete_missing | error | f + ptntable | insert_exists | keep_local | f + ptntable_1 | delete_missing | error | t + ptntable_1 | insert_exists | keep_local | t + ptntable_2 | delete_missing | error | t + ptntable_2 | insert_exists | keep_local | t + ptntable_2_1 | delete_missing | error | t + ptntable_2_1 | insert_exists | keep_local | t +(8 rows) + +ALTER TABLE ptntable_2 SET CONFLICT RESOLVER 'skip' ON 'delete_missing', + SET CONFLICT RESOLVER 'remote_apply' ON 'update_differ'; +--For both ptntable_2 and ptntable_2_1, expect: +--The resolver for 'delete_missing' changed from 'error' to 'skip' +--The 'update_differ'configuration added +SELECT relname, confrtype, confrres, confrinherited FROM pg_class pc, + (SELECT confrrelid, confrtype, confrres, confrinherited FROM pg_conflict_rel)conf + WHERE conf.confrrelid= pc.oid order by relname, confrtype; + relname | confrtype | confrres | confrinherited +--------------+----------------+--------------+---------------- + ptntable | delete_missing | error | f + ptntable | insert_exists | keep_local | f + ptntable_1 | delete_missing | error | t + ptntable_1 | insert_exists | keep_local | t + ptntable_2 | delete_missing | skip | f + ptntable_2 | insert_exists | keep_local | t + ptntable_2 | update_differ | remote_apply | f + ptntable_2_1 | delete_missing | skip | t + ptntable_2_1 | insert_exists | keep_local | t + ptntable_2_1 | update_differ | remote_apply | t +(10 rows) + +--Set resolvers on parent table again. It should not overwrite non inherited +--entries for ptntable_2 and corresponding entries for ptntable_2_1 +ALTER TABLE ptntable SET CONFLICT RESOLVER 'error' ON 'delete_missing', + SET CONFLICT RESOLVER 'remote_apply' ON 'insert_exists'; +SELECT relname, confrtype, confrres, confrinherited FROM pg_class pc, + (SELECT confrrelid, confrtype, confrres, confrinherited FROM pg_conflict_rel)conf + WHERE conf.confrrelid= pc.oid order by relname, confrtype; + relname | confrtype | confrres | confrinherited +--------------+----------------+--------------+---------------- + ptntable | delete_missing | error | f + ptntable | insert_exists | remote_apply | f + ptntable_1 | delete_missing | error | t + ptntable_1 | insert_exists | remote_apply | t + ptntable_2 | delete_missing | skip | f + ptntable_2 | insert_exists | remote_apply | t + ptntable_2 | update_differ | remote_apply | f + ptntable_2_1 | delete_missing | skip | t + ptntable_2_1 | insert_exists | remote_apply | t + ptntable_2_1 | update_differ | remote_apply | t +(10 rows) + +--Reset of inherited entry ON child alone should result in error +ALTER TABLE ptntable_2 RESET CONFLICT RESOLVER ON 'insert_exists'; +ERROR: cannot reset inherited resolver for conflict type "insert_exists" of relation "ptntable_2" +--Reset of non-inherited entry ON child alone should suceed +ALTER TABLE ptntable_2 RESET CONFLICT RESOLVER ON 'update_differ'; +SELECT relname, confrtype, confrres, confrinherited FROM pg_class pc, + (SELECT confrrelid, confrtype, confrres, confrinherited FROM pg_conflict_rel)conf + WHERE conf.confrrelid= pc.oid order by relname, confrtype; + relname | confrtype | confrres | confrinherited +--------------+----------------+--------------+---------------- + ptntable | delete_missing | error | f + ptntable | insert_exists | remote_apply | f + ptntable_1 | delete_missing | error | t + ptntable_1 | insert_exists | remote_apply | t + ptntable_2 | delete_missing | skip | f + ptntable_2 | insert_exists | remote_apply | t + ptntable_2_1 | delete_missing | skip | t + ptntable_2_1 | insert_exists | remote_apply | t +(8 rows) + +DROP TABLE ptntable_2; +--Expect entries removed for both ptntable_2 and its child ptntable_2_1 +SELECT relname, confrtype, confrres, confrinherited FROM pg_class pc, + (SELECT confrrelid, confrtype, confrres, confrinherited FROM pg_conflict_rel)conf + WHERE conf.confrrelid= pc.oid order by relname, confrtype; + relname | confrtype | confrres | confrinherited +------------+----------------+--------------+---------------- + ptntable | delete_missing | error | f + ptntable | insert_exists | remote_apply | f + ptntable_1 | delete_missing | error | t + ptntable_1 | insert_exists | remote_apply | t +(4 rows) + +-- Test for ALTER TABLE..SPLIT PARTITION and ALTER TABLE..MERGE PARTITIONS +ALTER TABLE ptntable SPLIT PARTITION ptntable_1 INTO + (PARTITION ptntable_1_1 FOR VALUES FROM ('2006-02-01') TO ('2006-02-10'), + PARTITION ptntable_1_10 FOR VALUES FROM ('2006-02-10') TO ('2006-02-20'), + PARTITION ptntable_1_20 FOR VALUES FROM ('2006-02-20') TO ('2006-03-01')); +--Expect split partitions to inherit resolvers FROM parent +SELECT relname, confrtype, confrres, confrinherited FROM pg_class pc, + (SELECT confrrelid, confrtype, confrres, confrinherited FROM pg_conflict_rel)conf + WHERE conf.confrrelid= pc.oid order by relname, confrtype; + relname | confrtype | confrres | confrinherited +---------------+----------------+--------------+---------------- + ptntable | delete_missing | error | f + ptntable | insert_exists | remote_apply | f + ptntable_1_1 | delete_missing | error | t + ptntable_1_1 | insert_exists | remote_apply | t + ptntable_1_10 | delete_missing | error | t + ptntable_1_10 | insert_exists | remote_apply | t + ptntable_1_20 | delete_missing | error | t + ptntable_1_20 | insert_exists | remote_apply | t +(8 rows) + +ALTER TABLE ptntable MERGE PARTITIONS (ptntable_1_1, ptntable_1_10, ptntable_1_20) + INTO ptntable_1; +--Expect merged partition to inherit resolvers FROM parent +SELECT relname, confrtype, confrres, confrinherited FROM pg_class pc, + (SELECT confrrelid, confrtype, confrres, confrinherited FROM pg_conflict_rel)conf + WHERE conf.confrrelid= pc.oid order by relname, confrtype; + relname | confrtype | confrres | confrinherited +------------+----------------+--------------+---------------- + ptntable | delete_missing | error | f + ptntable | insert_exists | remote_apply | f + ptntable_1 | delete_missing | error | t + ptntable_1 | insert_exists | remote_apply | t +(4 rows) + +-- Test for ALTER TABLE..ATTACH PARTITION and CREATE TABLE..PARTITION OF +CREATE TABLE ptntable_2( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +ALTER TABLE ptntable_2 SET CONFLICT RESOLVER 'remote_apply' ON 'update_differ', + SET CONFLICT RESOLVER 'skip' ON 'delete_missing'; +--Expect ptntable_2_1 to inherit resolvers of ptntable_2 +CREATE TABLE ptntable_2_1 PARTITION OF ptntable_2 + FOR VALUES FROM ('2006-03-20') TO ('2006-04-01'); +--Expect 4 new entries for ptntable_2 and its child ptntable_2_1 +--Entried for ptntable_2_1 should be marked as inherited. +SELECT relname, confrtype, confrres, confrinherited FROM pg_class pc, + (SELECT confrrelid, confrtype, confrres, confrinherited FROM pg_conflict_rel)conf + WHERE conf.confrrelid= pc.oid order by relname, confrtype; + relname | confrtype | confrres | confrinherited +--------------+----------------+--------------+---------------- + ptntable | delete_missing | error | f + ptntable | insert_exists | remote_apply | f + ptntable_1 | delete_missing | error | t + ptntable_1 | insert_exists | remote_apply | t + ptntable_2 | delete_missing | skip | f + ptntable_2 | update_differ | remote_apply | f + ptntable_2_1 | delete_missing | skip | t + ptntable_2_1 | update_differ | remote_apply | t +(8 rows) + +--Attach ptntable_2 to ptntable now +ALTER TABLE ptntable ATTACH PARTITION ptntable_2 + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); +--For both ptntable_2 and ptntable_2_1, expect: +--The 'insert_exists' configuration inherited +--Resolver for 'delete_missing' not overwritten +--Resolver for 'update_differ' retained. +SELECT relname, confrtype, confrres, confrinherited FROM pg_class pc, + (SELECT confrrelid, confrtype, confrres, confrinherited FROM pg_conflict_rel)conf + WHERE conf.confrrelid= pc.oid order by relname, confrtype; + relname | confrtype | confrres | confrinherited +--------------+----------------+--------------+---------------- + ptntable | delete_missing | error | f + ptntable | insert_exists | remote_apply | f + ptntable_1 | delete_missing | error | t + ptntable_1 | insert_exists | remote_apply | t + ptntable_2 | delete_missing | skip | f + ptntable_2 | insert_exists | remote_apply | t + ptntable_2 | update_differ | remote_apply | f + ptntable_2_1 | delete_missing | skip | t + ptntable_2_1 | insert_exists | remote_apply | t + ptntable_2_1 | update_differ | remote_apply | t +(10 rows) + +-- Test for ALTER TABLE..DETACH PARTITION +ALTER TABLE ptntable DETACH PARTITION ptntable_2; +--All resolvers of ptntable_2 should be marked as non-inherited +--All resolvers for ptntable_2_1's should still be marked as inherited (as +--they are still inherited from ptntable_2) +SELECT relname, confrtype, confrres, confrinherited FROM pg_class pc, + (SELECT confrrelid, confrtype, confrres, confrinherited FROM pg_conflict_rel)conf + WHERE conf.confrrelid= pc.oid order by relname, confrtype; + relname | confrtype | confrres | confrinherited +--------------+----------------+--------------+---------------- + ptntable | delete_missing | error | f + ptntable | insert_exists | remote_apply | f + ptntable_1 | delete_missing | error | t + ptntable_1 | insert_exists | remote_apply | t + ptntable_2 | delete_missing | skip | f + ptntable_2 | insert_exists | remote_apply | f + ptntable_2 | update_differ | remote_apply | f + ptntable_2_1 | delete_missing | skip | t + ptntable_2_1 | insert_exists | remote_apply | t + ptntable_2_1 | update_differ | remote_apply | t +(10 rows) + +DROP TABLE ptntable_2; +DROP TABLE ptntable; +--Expect no resolvers +SELECT relname, confrtype, confrres, confrinherited FROM pg_class pc, + (SELECT confrrelid, confrtype, confrres, confrinherited FROM pg_conflict_rel)conf + WHERE conf.confrrelid= pc.oid order by relname, confrtype; + relname | confrtype | confrres | confrinherited +---------+-----------+----------+---------------- +(0 rows) + diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out index 215eb899be..8caf852600 100644 --- a/src/test/regress/expected/oidjoins.out +++ b/src/test/regress/expected/oidjoins.out @@ -266,3 +266,4 @@ NOTICE: checking pg_subscription {subdbid} => pg_database {oid} NOTICE: checking pg_subscription {subowner} => pg_authid {oid} NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid} NOTICE: checking pg_subscription_rel {srrelid} => pg_class {oid} +NOTICE: checking pg_conflict_rel {confrrelid} => pg_class {oid} diff --git a/src/test/regress/sql/conflict_resolver.sql b/src/test/regress/sql/conflict_resolver.sql index f83d14b229..b4574d1df4 100644 --- a/src/test/regress/sql/conflict_resolver.sql +++ b/src/test/regress/sql/conflict_resolver.sql @@ -1,17 +1,19 @@ +-- +-- Test for configuration of global resolvers +-- + --check default global resolvers in system catalog -select * from pg_conflict order by conftype; +SELECT * FROM pg_conflict order by conftype; --- -- Test of SET/RESET CONFLICT RESOLVER with invalid names --- + SET CONFLICT RESOLVER 'keep_local' for 'aaaa'; -- fail SET CONFLICT RESOLVER 'bbbbb' for 'delete_missing'; -- fail SET CONFLICT RESOLVER 'remote_apply' for 'delete_missing'; -- fail RESET CONFLICT RESOLVER for 'ct'; -- fail --- -- Test of SET/RESET CONFLICT RESOLVER with valid names --- + SET CONFLICT RESOLVER 'error' for 'delete_missing'; SET CONFLICT RESOLVER 'keep_local' for 'insert_exists'; SET CONFLICT RESOLVER 'keep_local' for 'update_differ'; @@ -19,10 +21,155 @@ SET CONFLICT RESOLVER 'apply_or_error' for 'update_missing'; SET CONFLICT RESOLVER 'keep_local' for 'delete_differ'; --check new resolvers are saved -select * from pg_conflict order by conftype; +SELECT * FROM pg_conflict order by conftype; RESET CONFLICT RESOLVER for 'delete_missing'; RESET CONFLICT RESOLVER for 'insert_exists'; --check resolvers are reset to default for delete_missing and insert_exists -select * from pg_conflict order by conftype; +SELECT * FROM pg_conflict order by conftype; + +-- +-- Test for configuration of table level resolvers +-- + + +-- Test for ALTER TABLE..SET/RESET CONFLICT RESOLVER + +CREATE TABLE ptntable ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); + +CREATE TABLE ptntable_1 PARTITION OF ptntable + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); + +CREATE TABLE ptntable_2 PARTITION OF ptntable + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + PARTITION BY RANGE (logdate); + +CREATE TABLE ptntable_2_1 PARTITION OF ptntable_2 + FOR VALUES FROM ('2006-03-20') TO ('2006-04-01'); + +ALTER TABLE ptntable SET CONFLICT RESOLVER 'error' ON 'delete_missing', + SET CONFLICT RESOLVER 'keep_local' ON 'insert_exists'; + +--Expect 8 entries. 2 for parent and 2 for each child partition (inherited +--resolvers) +SELECT relname, confrtype, confrres, confrinherited FROM pg_class pc, + (SELECT confrrelid, confrtype, confrres, confrinherited FROM pg_conflict_rel)conf + WHERE conf.confrrelid= pc.oid order by relname, confrtype; + +ALTER TABLE ptntable_2 SET CONFLICT RESOLVER 'skip' ON 'delete_missing', + SET CONFLICT RESOLVER 'remote_apply' ON 'update_differ'; + +--For both ptntable_2 and ptntable_2_1, expect: +--The resolver for 'delete_missing' changed from 'error' to 'skip' +--The 'update_differ'configuration added +SELECT relname, confrtype, confrres, confrinherited FROM pg_class pc, + (SELECT confrrelid, confrtype, confrres, confrinherited FROM pg_conflict_rel)conf + WHERE conf.confrrelid= pc.oid order by relname, confrtype; + +--Set resolvers on parent table again. It should not overwrite non inherited +--entries for ptntable_2 and corresponding entries for ptntable_2_1 +ALTER TABLE ptntable SET CONFLICT RESOLVER 'error' ON 'delete_missing', + SET CONFLICT RESOLVER 'remote_apply' ON 'insert_exists'; + +SELECT relname, confrtype, confrres, confrinherited FROM pg_class pc, + (SELECT confrrelid, confrtype, confrres, confrinherited FROM pg_conflict_rel)conf + WHERE conf.confrrelid= pc.oid order by relname, confrtype; + +--Reset of inherited entry ON child alone should result in error +ALTER TABLE ptntable_2 RESET CONFLICT RESOLVER ON 'insert_exists'; + +--Reset of non-inherited entry ON child alone should suceed +ALTER TABLE ptntable_2 RESET CONFLICT RESOLVER ON 'update_differ'; + +SELECT relname, confrtype, confrres, confrinherited FROM pg_class pc, + (SELECT confrrelid, confrtype, confrres, confrinherited FROM pg_conflict_rel)conf + WHERE conf.confrrelid= pc.oid order by relname, confrtype; + +DROP TABLE ptntable_2; + +--Expect entries removed for both ptntable_2 and its child ptntable_2_1 +SELECT relname, confrtype, confrres, confrinherited FROM pg_class pc, + (SELECT confrrelid, confrtype, confrres, confrinherited FROM pg_conflict_rel)conf + WHERE conf.confrrelid= pc.oid order by relname, confrtype; + + +-- Test for ALTER TABLE..SPLIT PARTITION and ALTER TABLE..MERGE PARTITIONS + +ALTER TABLE ptntable SPLIT PARTITION ptntable_1 INTO + (PARTITION ptntable_1_1 FOR VALUES FROM ('2006-02-01') TO ('2006-02-10'), + PARTITION ptntable_1_10 FOR VALUES FROM ('2006-02-10') TO ('2006-02-20'), + PARTITION ptntable_1_20 FOR VALUES FROM ('2006-02-20') TO ('2006-03-01')); + +--Expect split partitions to inherit resolvers FROM parent +SELECT relname, confrtype, confrres, confrinherited FROM pg_class pc, + (SELECT confrrelid, confrtype, confrres, confrinherited FROM pg_conflict_rel)conf + WHERE conf.confrrelid= pc.oid order by relname, confrtype; + +ALTER TABLE ptntable MERGE PARTITIONS (ptntable_1_1, ptntable_1_10, ptntable_1_20) + INTO ptntable_1; + +--Expect merged partition to inherit resolvers FROM parent +SELECT relname, confrtype, confrres, confrinherited FROM pg_class pc, + (SELECT confrrelid, confrtype, confrres, confrinherited FROM pg_conflict_rel)conf + WHERE conf.confrrelid= pc.oid order by relname, confrtype; + + +-- Test for ALTER TABLE..ATTACH PARTITION and CREATE TABLE..PARTITION OF + +CREATE TABLE ptntable_2( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); + +ALTER TABLE ptntable_2 SET CONFLICT RESOLVER 'remote_apply' ON 'update_differ', + SET CONFLICT RESOLVER 'skip' ON 'delete_missing'; + +--Expect ptntable_2_1 to inherit resolvers of ptntable_2 +CREATE TABLE ptntable_2_1 PARTITION OF ptntable_2 + FOR VALUES FROM ('2006-03-20') TO ('2006-04-01'); + +--Expect 4 new entries for ptntable_2 and its child ptntable_2_1 +--Entried for ptntable_2_1 should be marked as inherited. +SELECT relname, confrtype, confrres, confrinherited FROM pg_class pc, + (SELECT confrrelid, confrtype, confrres, confrinherited FROM pg_conflict_rel)conf + WHERE conf.confrrelid= pc.oid order by relname, confrtype; + +--Attach ptntable_2 to ptntable now +ALTER TABLE ptntable ATTACH PARTITION ptntable_2 + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); + +--For both ptntable_2 and ptntable_2_1, expect: +--The 'insert_exists' configuration inherited +--Resolver for 'delete_missing' not overwritten +--Resolver for 'update_differ' retained. +SELECT relname, confrtype, confrres, confrinherited FROM pg_class pc, + (SELECT confrrelid, confrtype, confrres, confrinherited FROM pg_conflict_rel)conf + WHERE conf.confrrelid= pc.oid order by relname, confrtype; + + +-- Test for ALTER TABLE..DETACH PARTITION + +ALTER TABLE ptntable DETACH PARTITION ptntable_2; + +--All resolvers of ptntable_2 should be marked as non-inherited +--All resolvers for ptntable_2_1's should still be marked as inherited (as +--they are still inherited from ptntable_2) +SELECT relname, confrtype, confrres, confrinherited FROM pg_class pc, + (SELECT confrrelid, confrtype, confrres, confrinherited FROM pg_conflict_rel)conf + WHERE conf.confrrelid= pc.oid order by relname, confrtype; + +DROP TABLE ptntable_2; +DROP TABLE ptntable; + +--Expect no resolvers +SELECT relname, confrtype, confrres, confrinherited FROM pg_class pc, + (SELECT confrrelid, confrtype, confrres, confrinherited FROM pg_conflict_rel)conf + WHERE conf.confrrelid= pc.oid order by relname, confrtype; -- 2.34.1