diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 354fbe4b4b..93c313dee6 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -335,13 +335,16 @@ handle_streamed_transaction(LogicalRepMsgType action, StringInfo s)
 }
 
 /*
- * Executor state preparation for evaluation of constraint expressions,
- * indexes and triggers.
+ * Prepares an EState containing just enough information for the caller to
+ * use the executor for evaluating the target relation's constraints, update
+ * indexes, and fire any AFTER triggers.
  *
- * This is based on similar code in copy.c
+ * A ResultRelInfo for the relation to be passed to executor routines is
+ * returned in *resultRelInfo.
  */
 static EState *
-create_estate_for_relation(LogicalRepRelMapEntry *rel)
+create_estate_for_relation(LogicalRepRelMapEntry *rel,
+						   ResultRelInfo **resultRelInfo)
 {
 	EState	   *estate;
 	RangeTblEntry *rte;
@@ -355,6 +358,9 @@ create_estate_for_relation(LogicalRepRelMapEntry *rel)
 	rte->rellockmode = AccessShareLock;
 	ExecInitRangeTable(estate, list_make1(rte));
 
+	*resultRelInfo = makeNode(ResultRelInfo);
+	ExecInitResultRelation(estate, *resultRelInfo, 1);
+
 	estate->es_output_cid = GetCurrentCommandId(true);
 
 	/* Prepare to catch AFTER triggers. */
@@ -363,6 +369,19 @@ create_estate_for_relation(LogicalRepRelMapEntry *rel)
 	return estate;
 }
 
+/* Winds down the executor created in create_estate_for_relation(). */
+static void
+close_estate(EState *estate)
+{
+	/* Handle any queued AFTER triggers. */
+	AfterTriggerEndQuery(estate);
+
+	/* Cleanup. */
+	ExecResetTupleTable(estate->es_tupleTable, false);
+	ExecCloseRangeTableRelations(estate);
+	FreeExecutorState(estate);
+}
+
 /*
  * Executes default values for columns for which we can't map to remote
  * relation columns.
@@ -1168,12 +1187,10 @@ apply_handle_insert(StringInfo s)
 	}
 
 	/* Initialize the executor state. */
-	estate = create_estate_for_relation(rel);
+	estate = create_estate_for_relation(rel, &resultRelInfo);
 	remoteslot = ExecInitExtraTupleSlot(estate,
 										RelationGetDescr(rel->localrel),
 										&TTSOpsVirtual);
-	resultRelInfo = makeNode(ResultRelInfo);
-	InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
 
 	/* Input functions may need an active snapshot, so get one */
 	PushActiveSnapshot(GetTransactionSnapshot());
@@ -1194,11 +1211,7 @@ apply_handle_insert(StringInfo s)
 
 	PopActiveSnapshot();
 
-	/* Handle queued AFTER triggers. */
-	AfterTriggerEndQuery(estate);
-
-	ExecResetTupleTable(estate->es_tupleTable, false);
-	FreeExecutorState(estate);
+	close_estate(estate);
 
 	logicalrep_rel_close(rel, NoLock);
 
@@ -1293,12 +1306,10 @@ apply_handle_update(StringInfo s)
 	check_relation_updatable(rel);
 
 	/* Initialize the executor state. */
-	estate = create_estate_for_relation(rel);
+	estate = create_estate_for_relation(rel, &resultRelInfo);
 	remoteslot = ExecInitExtraTupleSlot(estate,
 										RelationGetDescr(rel->localrel),
 										&TTSOpsVirtual);
-	resultRelInfo = makeNode(ResultRelInfo);
-	InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
 
 	/*
 	 * Populate updatedCols so that per-column triggers can fire, and so
@@ -1345,11 +1356,7 @@ apply_handle_update(StringInfo s)
 
 	PopActiveSnapshot();
 
-	/* Handle queued AFTER triggers. */
-	AfterTriggerEndQuery(estate);
-
-	ExecResetTupleTable(estate->es_tupleTable, false);
-	FreeExecutorState(estate);
+	close_estate(estate);
 
 	logicalrep_rel_close(rel, NoLock);
 
@@ -1450,12 +1457,10 @@ apply_handle_delete(StringInfo s)
 	check_relation_updatable(rel);
 
 	/* Initialize the executor state. */
-	estate = create_estate_for_relation(rel);
+	estate = create_estate_for_relation(rel, &resultRelInfo);
 	remoteslot = ExecInitExtraTupleSlot(estate,
 										RelationGetDescr(rel->localrel),
 										&TTSOpsVirtual);
-	resultRelInfo = makeNode(ResultRelInfo);
-	InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
 
 	PushActiveSnapshot(GetTransactionSnapshot());
 
@@ -1474,11 +1479,7 @@ apply_handle_delete(StringInfo s)
 
 	PopActiveSnapshot();
 
-	/* Handle queued AFTER triggers. */
-	AfterTriggerEndQuery(estate);
-
-	ExecResetTupleTable(estate->es_tupleTable, false);
-	FreeExecutorState(estate);
+	close_estate(estate);
 
 	logicalrep_rel_close(rel, NoLock);
 
