diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index a314821..19cf119 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -1859,9 +1859,6 @@ postgresExecForeignInsert(EState *estate,
 	PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
 	TupleTableSlot *rslot;
 
-	Assert(!resultRelInfo->ri_usesBulkModify ||
-		   resultRelInfo->ri_FdwRoutine->BeginForeignCopyIn == NULL);
-
 	/*
 	 * If the fmstate has aux_fmstate set, use the aux_fmstate (see
 	 * postgresBeginForeignInsert())
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index fd1f1d6..43cd8c0 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -86,16 +86,6 @@ typedef enum EolType
 } EolType;
 
 /*
- * Represents the heap insert method to be used during COPY FROM.
- */
-typedef enum CopyInsertMethod
-{
-	CIM_SINGLE,					/* use table_tuple_insert or fdw routine */
-	CIM_MULTI,					/* always use table_multi_insert */
-	CIM_MULTI_CONDITIONAL		/* use table_multi_insert only if valid */
-} CopyInsertMethod;
-
-/*
  * This struct contains all the state variables used throughout a COPY
  * operation. For simplicity, we use the same struct for all variants of COPY,
  * even though some fields are used in only some cases.
@@ -2762,12 +2752,11 @@ CopyFrom(CopyState cstate)
 	CommandId	mycid = GetCurrentCommandId(true);
 	int			ti_options = 0; /* start with default options for insert */
 	BulkInsertState bistate = NULL;
-	CopyInsertMethod insertMethod;
+	bool		use_multi_insert;
 	CopyMultiInsertInfo multiInsertInfo = {0};	/* pacify compiler */
 	uint64		processed = 0;
 	bool		has_before_insert_row_trig;
 	bool		has_instead_insert_row_trig;
-	bool		leafpart_use_multi_insert = false;
 
 	Assert(cstate->rel);
 
@@ -2868,6 +2857,52 @@ CopyFrom(CopyState cstate)
 	}
 
 	/*
+	 * It's generally more efficient to prepare a bunch of tuples for
+	 * insertion, and insert them in bulk, for example, with one
+	 * table_multi_insert() call than call table_tuple_insert() separately
+	 * for every tuple. However, there are a number of reasons why we might
+	 * not be able to do this.  We check some conditions below while some
+	 * other target relation properties are left for InitResultRelInfo() to
+	 * check, because they must also be checked for partitions which are
+	 * initialized later.
+	 */
+	if (cstate->volatile_defexprs || list_length(cstate->attnumlist) == 0)
+	{
+		/*
+		 * Can't support bufferization of copy into foreign tables without any
+		 * defined columns or if there are any volatile default expressions in the
+		 * table. Similarly to the trigger case above, such expressions may query
+		 * the table we're inserting into.
+		 *
+		 * Note: It does not matter if any partitions have any volatile
+		 * default expressions as we use the defaults from the target of the
+		 * COPY command.
+		 */
+		use_multi_insert = false;
+	}
+	else if (contain_volatile_functions(cstate->whereClause))
+	{
+		/*
+		 * Can't support multi-inserts if there are any volatile function
+		 * expressions in WHERE clause.  Similarly to the trigger case above,
+		 * such expressions may query the table we're inserting into.
+		 */
+		use_multi_insert = false;
+	}
+	else
+	{
+		/*
+		 * Looks okay to try multi-insert, but that may change once we
+		 * check few more properties in InitResultRelInfo().
+		 *
+		 * For partitioned tables, whether or not to use multi-insert depends
+		 * on the individual parition's properties which are also checked in
+		 * InitResultRelInfo().
+		 */
+		use_multi_insert = true;
+	}
+
+	/*
 	 * We need a ResultRelInfo so we can use the regular executor's
 	 * index-entry-making machinery.  (There used to be a huge amount of code
 	 * here that basically duplicated execUtils.c ...)
@@ -2877,6 +2912,7 @@ CopyFrom(CopyState cstate)
 					  cstate->rel,
 					  1,		/* must match rel's position in range_table */
 					  NULL,
+					  use_multi_insert,
 					  0);
 	target_resultRelInfo = resultRelInfo;
 
@@ -2928,85 +2964,9 @@ CopyFrom(CopyState cstate)
 		cstate->qualexpr = ExecInitQual(castNode(List, cstate->whereClause),
 										&mtstate->ps);
 
-	/*
-	 * It's generally more efficient to prepare a bunch of tuples for
-	 * insertion, and insert them in one table_multi_insert() call, than call
-	 * table_tuple_insert() separately for every tuple. However, there are a
-	 * number of reasons why we might not be able to do this.  These are
-	 * explained below.
-	 */
-	if (resultRelInfo->ri_TrigDesc != NULL &&
-		(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
-		 resultRelInfo->ri_TrigDesc->trig_insert_instead_row))
-	{
-		/*
-		 * Can't support multi-inserts when there are any BEFORE/INSTEAD OF
-		 * triggers on the table. Such triggers might query the table we're
-		 * inserting into and act differently if the tuples that have already
-		 * been processed and prepared for insertion are not there.
-		 */
-		insertMethod = CIM_SINGLE;
-	}
-	else if (proute != NULL && resultRelInfo->ri_TrigDesc != NULL &&
-			 resultRelInfo->ri_TrigDesc->trig_insert_new_table)
-	{
-		/*
-		 * For partitioned tables we can't support multi-inserts when there
-		 * are any statement level insert triggers. It might be possible to
-		 * allow partitioned tables with such triggers in the future, but for
-		 * now, CopyMultiInsertInfoFlush expects that any before row insert
-		 * and statement level insert triggers are on the same relation.
-		 */
-		insertMethod = CIM_SINGLE;
-	}
-	else if (cstate->volatile_defexprs || list_length(cstate->attnumlist) == 0)
-	{
-		/*
-		 * Can't support bufferization of copy into foreign tables without any
-		 * defined columns or if there are any volatile default expressions in the
-		 * table. Similarly to the trigger case above, such expressions may query
-		 * the table we're inserting into.
-		 *
-		 * Note: It does not matter if any partitions have any volatile
-		 * default expressions as we use the defaults from the target of the
-		 * COPY command.
-		 */
-		insertMethod = CIM_SINGLE;
-	}
-	else if (contain_volatile_functions(cstate->whereClause))
-	{
-		/*
-		 * Can't support multi-inserts if there are any volatile function
-		 * expressions in WHERE clause.  Similarly to the trigger case above,
-		 * such expressions may query the table we're inserting into.
-		 */
-		insertMethod = CIM_SINGLE;
-	}
-	else
-	{
-		/*
-		 * For partitioned tables, we may still be able to perform bulk
-		 * inserts.  However, the possibility of this depends on which types
-		 * of triggers exist on the partition.  We must disable bulk inserts
-		 * if the partition is a foreign table or it has any before row insert
-		 * or insert instead triggers (same as we checked above for the parent
-		 * table).  Since the partition's resultRelInfos are initialized only
-		 * when we actually need to insert the first tuple into them, we must
-		 * have the intermediate insert method of CIM_MULTI_CONDITIONAL to
-		 * flag that we must later determine if we can use bulk-inserts for
-		 * the partition being inserted into.
-		 */
-		if (proute)
-			insertMethod = CIM_MULTI_CONDITIONAL;
-		else
-			insertMethod = CIM_MULTI;
-
+	if (resultRelInfo->ri_usesMultiInsert)
 		CopyMultiInsertInfoInit(&multiInsertInfo, resultRelInfo, cstate,
 								estate, mycid, ti_options);
-	}
-
-	if (insertMethod != CIM_SINGLE)
-		resultRelInfo->ri_usesBulkModify = true;
 
 	/*
 	 * Init COPY into foreign table. Initialization of copying into foreign
@@ -3014,7 +2974,7 @@ CopyFrom(CopyState cstate)
 	 */
 	if (target_resultRelInfo->ri_FdwRoutine != NULL)
 	{
-		if (target_resultRelInfo->ri_usesBulkModify &&
+		if (target_resultRelInfo->ri_usesMultiInsert &&
 			target_resultRelInfo->ri_FdwRoutine->BeginForeignCopyIn != NULL)
 			target_resultRelInfo->ri_FdwRoutine->BeginForeignCopyIn(mtstate,
 																resultRelInfo);
@@ -3029,7 +2989,7 @@ CopyFrom(CopyState cstate)
 	 * one, even if we might batch insert, to read the tuple in the root
 	 * partition's form.
 	 */
-	if (insertMethod == CIM_SINGLE || insertMethod == CIM_MULTI_CONDITIONAL)
+	if (!resultRelInfo->ri_usesMultiInsert || proute)
 	{
 		singleslot = table_slot_create(resultRelInfo->ri_RelationDesc,
 									   &estate->es_tupleTable);
@@ -3072,7 +3032,7 @@ CopyFrom(CopyState cstate)
 		ResetPerTupleExprContext(estate);
 
 		/* select slot to (initially) load row into */
-		if (insertMethod == CIM_SINGLE || proute)
+		if (!target_resultRelInfo->ri_usesMultiInsert || proute)
 		{
 			myslot = singleslot;
 			Assert(myslot != NULL);
@@ -3080,7 +3040,6 @@ CopyFrom(CopyState cstate)
 		else
 		{
 			Assert(resultRelInfo == target_resultRelInfo);
-			Assert(insertMethod == CIM_MULTI);
 
 			myslot = CopyMultiInsertInfoNextFreeSlot(&multiInsertInfo,
 													 resultRelInfo);
@@ -3139,24 +3098,14 @@ CopyFrom(CopyState cstate)
 				has_instead_insert_row_trig = (resultRelInfo->ri_TrigDesc &&
 											   resultRelInfo->ri_TrigDesc->trig_insert_instead_row);
 
-				/*
-				 * Disable multi-inserts when the partition has BEFORE/INSTEAD
-				 * OF triggers, or if the partition is a foreign partition.
-				 */
-				leafpart_use_multi_insert = insertMethod == CIM_MULTI_CONDITIONAL &&
-					!has_before_insert_row_trig &&
-					!has_instead_insert_row_trig &&
-					(resultRelInfo->ri_FdwRoutine == NULL || resultRelInfo->ri_usesBulkModify);
-
 				/* Set the multi-insert buffer to use for this partition. */
-				if (leafpart_use_multi_insert)
+				if (resultRelInfo->ri_usesMultiInsert)
 				{
 					if (resultRelInfo->ri_CopyMultiInsertBuffer == NULL)
 						CopyMultiInsertInfoSetupBuffer(&multiInsertInfo,
 													   resultRelInfo);
 				}
-				else if (insertMethod == CIM_MULTI_CONDITIONAL &&
-						 !CopyMultiInsertInfoIsEmpty(&multiInsertInfo))
+				else if (!CopyMultiInsertInfoIsEmpty(&multiInsertInfo))
 				{
 					/*
 					 * Flush pending inserts if this partition can't use
@@ -3208,7 +3157,7 @@ CopyFrom(CopyState cstate)
 			 * rowtype.
 			 */
 			map = resultRelInfo->ri_PartitionInfo->pi_RootToPartitionMap;
-			if (insertMethod == CIM_SINGLE || !leafpart_use_multi_insert)
+			if (!resultRelInfo->ri_usesMultiInsert)
 			{
 				/* non batch insert */
 				if (map != NULL)
@@ -3227,9 +3176,6 @@ CopyFrom(CopyState cstate)
 				 */
 				TupleTableSlot *batchslot;
 
-				/* no other path available for partitioned table */
-				Assert(insertMethod == CIM_MULTI_CONDITIONAL);
-
 				batchslot = CopyMultiInsertInfoNextFreeSlot(&multiInsertInfo,
 															resultRelInfo);
 
@@ -3300,7 +3246,7 @@ CopyFrom(CopyState cstate)
 					ExecPartitionCheck(resultRelInfo, myslot, estate, true);
 
 				/* Store the slot in the multi-insert buffer, when enabled. */
-				if (insertMethod == CIM_MULTI || leafpart_use_multi_insert)
+				if (resultRelInfo->ri_usesMultiInsert)
 				{
 					/*
 					 * The slot previously might point into the per-tuple
@@ -3375,11 +3321,8 @@ CopyFrom(CopyState cstate)
 	}
 
 	/* Flush any remaining buffered tuples */
-	if (insertMethod != CIM_SINGLE)
-	{
-		if (!CopyMultiInsertInfoIsEmpty(&multiInsertInfo))
-			CopyMultiInsertInfoFlush(&multiInsertInfo, NULL);
-	}
+	if (!CopyMultiInsertInfoIsEmpty(&multiInsertInfo))
+		CopyMultiInsertInfoFlush(&multiInsertInfo, NULL);
 
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
@@ -3407,19 +3350,17 @@ CopyFrom(CopyState cstate)
 	/* Allow the FDW to shut down */
 	if (target_resultRelInfo->ri_FdwRoutine != NULL)
 	{
-		if (target_resultRelInfo->ri_usesBulkModify &&
+		if (target_resultRelInfo->ri_usesMultiInsert &&
 			target_resultRelInfo->ri_FdwRoutine->EndForeignCopyIn != NULL)
 			target_resultRelInfo->ri_FdwRoutine->EndForeignCopyIn(estate,
 														target_resultRelInfo);
 		else if (target_resultRelInfo->ri_FdwRoutine->EndForeignInsert != NULL)
 			target_resultRelInfo->ri_FdwRoutine->EndForeignInsert(estate,
 														target_resultRelInfo);
-		target_resultRelInfo->ri_usesBulkModify = false;
 	}
 
 	/* Tear down the multi-insert buffer data */
-	if (insertMethod != CIM_SINGLE)
-		CopyMultiInsertInfoCleanup(&multiInsertInfo);
+	CopyMultiInsertInfoCleanup(&multiInsertInfo);
 
 	ExecCloseIndices(target_resultRelInfo);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ac53f79..f071b56 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1786,6 +1786,7 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 						  rel,
 						  0,	/* dummy rangetable index */
 						  NULL,
+						  false,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b8b09d5..e08c8b2 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -851,6 +851,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 							  resultRelation,
 							  resultRelationIndex,
 							  NULL,
+							  false,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -883,6 +884,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 								  resultRelDesc,
 								  resultRelIndex,
 								  NULL,
+								  false,
 								  estate->es_instrument);
 				resultRelInfo++;
 			}
@@ -1278,6 +1280,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
 				  Relation partition_root,
+				  bool use_multi_insert,
 				  int instrument_options)
 {
 	List	   *partition_check = NIL;
@@ -1345,7 +1348,55 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_PartitionRoot = partition_root;
 	resultRelInfo->ri_PartitionInfo = NULL; /* may be set later */
 	resultRelInfo->ri_CopyMultiInsertBuffer = NULL;
-	resultRelInfo->ri_usesBulkModify = false;
+
+	/*
+	 * If the caller has asked to use "multi-insert" mode, check if the
+	 * relation allows it and if it does set ri_usesMultiInsert to true.
+	 */
+	if (!use_multi_insert)
+	{
+		/* Caller didn't ask for it. */
+		resultRelInfo->ri_usesMultiInsert = false;
+	}
+	else if (resultRelInfo->ri_TrigDesc != NULL &&
+			 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
+			  resultRelInfo->ri_TrigDesc->trig_insert_instead_row))
+	{
+		/*
+		 * Can't support multi-inserts when there are any BEFORE/INSTEAD OF
+		 * triggers on the table. Such triggers might query the table we're
+		 * inserting into and act differently if the tuples that have already
+		 * been processed and prepared for insertion are not there.
+		 */
+		resultRelInfo->ri_usesMultiInsert = false;
+	}
+	else if (resultRelationDesc->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+			 resultRelInfo->ri_TrigDesc != NULL &&
+			 resultRelInfo->ri_TrigDesc->trig_insert_new_table)
+	{
+		/*
+		 * For partitioned tables we can't support multi-inserts when there
+		 * are any statement level insert triggers. It might be possible to
+		 * allow partitioned tables with such triggers in the future, but for
+		 * now, CopyMultiInsertInfoFlush expects that any before row insert
+		 * and statement level insert triggers are on the same relation.
+		 */
+		resultRelInfo->ri_usesMultiInsert = false;
+	}
+	else if (resultRelInfo->ri_FdwRoutine != NULL &&
+			 resultRelInfo->ri_FdwRoutine->ExecForeignCopyIn == NULL)
+	{
+		/*
+		 * For a foreign table, we can't support multi-inserts unless its FDW
+		 * provides the necessary COPY interface.
+		 */
+		resultRelInfo->ri_usesMultiInsert = false;
+	}
+	else
+	{
+		/* OK, caller can use multi-insert on this relation. */
+		resultRelInfo->ri_usesMultiInsert = true;
+	}
 }
 
 /*
@@ -1435,6 +1486,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 					  rel,
 					  0,		/* dummy rangetable index */
 					  NULL,
+					  false,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 6a4b947..7b72a09 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -524,13 +524,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 					  partrel,
 					  node ? node->rootRelation : 1,
 					  rootrel,
+					  rootResultRelInfo->ri_usesMultiInsert,
 					  estate->es_instrument);
 
-	if (rootResultRelInfo->ri_usesBulkModify &&
-		leaf_part_rri->ri_FdwRoutine != NULL &&
-		leaf_part_rri->ri_FdwRoutine->BeginForeignCopyIn != NULL)
-		leaf_part_rri->ri_usesBulkModify = true;
-
 	/*
 	 * Verify result relation is a valid target for an INSERT.  An UPDATE of a
 	 * partition-key becomes a DELETE+INSERT operation, so this check is still
@@ -944,11 +940,9 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
 	 */
 	if (partRelInfo->ri_FdwRoutine != NULL)
 	{
-		if (partRelInfo->ri_usesBulkModify)
-		{
-			Assert(partRelInfo->ri_FdwRoutine->BeginForeignCopyIn != NULL);
+		if (partRelInfo->ri_usesMultiInsert &&
+			partRelInfo->ri_FdwRoutine->BeginForeignCopyIn != NULL)
 			partRelInfo->ri_FdwRoutine->BeginForeignCopyIn(mtstate, partRelInfo);
-		}
 		else if (partRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)
 			partRelInfo->ri_FdwRoutine->BeginForeignInsert(mtstate, partRelInfo);
 	}
@@ -1135,7 +1129,7 @@ ExecCleanupTupleRouting(ModifyTableState *mtstate,
 		/* Allow any FDWs to shut down */
 		if (resultRelInfo->ri_FdwRoutine != NULL)
 		{
-			if (resultRelInfo->ri_usesBulkModify)
+			if (resultRelInfo->ri_usesMultiInsert)
 			{
 				Assert(resultRelInfo->ri_FdwRoutine->EndForeignCopyIn != NULL);
 				resultRelInfo->ri_FdwRoutine->EndForeignCopyIn(mtstate->ps.state,
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 2fcf2e6..d33c9c9 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -211,7 +211,7 @@ create_estate_for_relation(LogicalRepRelMapEntry *rel)
 	ExecInitRangeTable(estate, list_make1(rte));
 
 	resultRelInfo = makeNode(ResultRelInfo);
-	InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
+	InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, false, 0);
 
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 415e117..72612bd 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -189,6 +189,7 @@ extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 							  Relation resultRelationDesc,
 							  Index resultRelationIndex,
 							  Relation partition_root,
+							  bool use_multi_insert,
 							  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern void ExecCleanUpTriggerState(EState *estate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb3b2f0..1d79b6a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -489,16 +489,15 @@ typedef struct ResultRelInfo
 	/* Additional information specific to partition tuple routing */
 	struct PartitionRoutingInfo *ri_PartitionInfo;
 
-	/* For use by copy.c when performing multi-inserts */
-	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
-
 	/*
-	 * For use by copy.c:
-	 * for partitioned relation "true" means that child relations are allowed for
-	 * using bulk modify operations; for foreign relation (or foreign partition
-	 * of) "true" value means that modify operations must use bulk FDW API.
+	 * The following fields are currently only relevant to copy.c.
+	 *
+	 * True if okay to use multi-insert on this relation
 	 */
-	bool ri_usesBulkModify;
+	bool ri_usesMultiInsert;
+
+	/* Buffer allocated to this relation when using multi-insert mode */
+	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 } ResultRelInfo;
 
 /* ----------------
