From ed9ec14fa145a4fedf0571bd1098264d89657f20 Mon Sep 17 00:00:00 2001
From: Shveta Malik <shveta.malik@gmail.com>
Date: Wed, 17 Jul 2024 10:13:38 +0530
Subject: [PATCH v6 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 <name> SET CONFLICT RESOLVER <resolver1> on <conflict_type1>,
                   SET CONFLICT RESOLVER <resolver2> on <conflict_type2>, ...;

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 <name> RESET CONFLICT RESOLVER on <conflict_type1>,
                   RESET CONFLICT RESOLVER on <conflict_type2>, ...;

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            |  12 +
 .../regress/expected/conflict_resolver.out    | 245 ++++++++++++-
 src/test/regress/expected/oidjoins.out        |   1 +
 src/test/regress/sql/conflict_resolver.sql    | 161 ++++++++-
 14 files changed, 928 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 85a7b7e641..1456eedf46 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 721d24783b..4ebd80950a 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),
@@ -20775,3 +20818,30 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 }
+
+/*
+ * ALTER TABLE <name> 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 <name> 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 <name> SET CONFLICT RESOLVER <conflict_resolver>
+			 *		on <conflict_type>
+			 */
+			| 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 <name> RESET CONFLICT RESOLVER on <conflict_type> */
+			| 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 a8fa470cf7..7ec9fd6dd8 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"
 
@@ -320,9 +325,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;
@@ -521,10 +526,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));
@@ -552,7 +556,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);
@@ -632,3 +636,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, &copy_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 4e2853b5be..207c4cd9a4 100644
--- a/src/include/replication/conflict.h
+++ b/src/include/replication/conflict.h
@@ -92,4 +92,16 @@ extern ConflictResolver GetConflictResolver(TupleTableSlot *localslot,
 											bool *apply_remote,
 											LogicalRepTupleData *newtup);
 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

