From bc70b782794935dabc76c02409c3b17d36df0a01 Mon Sep 17 00:00:00 2001
From: "dgrowley@gmail.com" <dgrowley@gmail.com>
Date: Thu, 27 Sep 2018 16:14:41 +1200
Subject: [PATCH v6 4/4] Revise executor range table relation opening/closing

All requests to open range table relations in the executor now go
through ExecRangeTableRelation(), which consults an array of Relation
pointers indexed by RT index, an arrangement which allows the executor
to have to heap_open any given range table relation only once.

To speed up retrieving range table entries at arbitrary points within
the executor, this introduceds an array of RangeTblEntry pointers in
EState.  InitPlan builds it from the es_range_table list.

This also revises PartitionedRelPruneInfo node to contain the
partitioned table's RT index instead of OID.  With that change,
ExecCreatePartitionPruneState can use ExecRangeTableRelation described
above.

Accessed Relations are now also closed during ExecEndPlan(). Callers of
ExecRangeTableRelation() have no need to consider how their Relation
objects are cleaned up.

Authors: Amit Langote, David Rowley
---
 contrib/postgres_fdw/postgres_fdw.c         | 16 +++---
 src/backend/commands/copy.c                 |  3 +-
 src/backend/commands/trigger.c              |  3 +-
 src/backend/executor/README                 |  4 +-
 src/backend/executor/execExprInterp.c       |  7 +--
 src/backend/executor/execMain.c             | 80 ++++++++++-------------------
 src/backend/executor/execPartition.c        | 39 +++-----------
 src/backend/executor/execUtils.c            | 67 +++++++++++++++---------
 src/backend/executor/nodeAppend.c           |  6 ---
 src/backend/executor/nodeBitmapHeapscan.c   |  7 ---
 src/backend/executor/nodeCustom.c           |  4 --
 src/backend/executor/nodeForeignscan.c      |  4 --
 src/backend/executor/nodeIndexonlyscan.c    |  7 ---
 src/backend/executor/nodeIndexscan.c        |  7 ---
 src/backend/executor/nodeLockRows.c         |  2 +-
 src/backend/executor/nodeMergeAppend.c      |  6 ---
 src/backend/executor/nodeModifyTable.c      | 26 +++++-----
 src/backend/executor/nodeSamplescan.c       |  5 --
 src/backend/executor/nodeSeqscan.c          |  7 ---
 src/backend/executor/nodeTidscan.c          |  5 --
 src/backend/nodes/copyfuncs.c               |  2 +-
 src/backend/nodes/outfuncs.c                |  2 +-
 src/backend/nodes/readfuncs.c               |  2 +-
 src/backend/optimizer/plan/setrefs.c        | 30 +++++++++++
 src/backend/partitioning/partprune.c        |  5 +-
 src/backend/replication/logical/tablesync.c |  9 +++-
 src/backend/replication/logical/worker.c    |  2 +
 src/include/executor/execPartition.h        |  1 -
 src/include/executor/executor.h             | 24 ++++++++-
 src/include/nodes/execnodes.h               | 23 ++++++++-
 src/include/nodes/plannodes.h               |  2 +-
 src/include/parser/parsetree.h              | 10 ----
 32 files changed, 194 insertions(+), 223 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index eeb053d5d8..d20c75367c 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -1345,7 +1345,7 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 		rtindex = fsplan->scan.scanrelid;
 	else
 		rtindex = bms_next_member(fsplan->fs_relids, -1);
-	rte = rt_fetch(rtindex, estate->es_range_table);
+	rte = exec_rt_fetch(rtindex, estate->es_range_table_array);
 	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
@@ -1731,8 +1731,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 										FdwModifyPrivateRetrievedAttrs);
 
 	/* Find RTE. */
-	rte = rt_fetch(resultRelInfo->ri_RangeTableIndex,
-				   mtstate->ps.state->es_range_table);
+	rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
+						mtstate->ps.state->es_range_table_array);
 
 	/* Construct an execution state. */
 	fmstate = create_foreign_modify(mtstate->ps.state,
@@ -2036,7 +2036,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	 * correspond to this partition if it is one of the UPDATE subplan target
 	 * rels; in that case, we can just use the existing RTE as-is.
 	 */
-	rte = list_nth(estate->es_range_table, resultRelation - 1);
+	rte = exec_rt_fetch(resultRelation, estate->es_range_table_array);
 	if (rte->relid != RelationGetRelid(rel))
 	{
 		rte = copyObject(rte);
@@ -2396,7 +2396,7 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	 * ExecCheckRTEPerms() does.
 	 */
 	rtindex = estate->es_result_relation_info->ri_RangeTableIndex;
-	rte = rt_fetch(rtindex, estate->es_range_table);
+	rte = exec_rt_fetch(rtindex, estate->es_range_table_array);
 	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
@@ -2546,10 +2546,6 @@ postgresEndDirectModify(ForeignScanState *node)
 	ReleaseConnection(dmstate->conn);
 	dmstate->conn = NULL;
 
-	/* close the target relation. */
-	if (dmstate->resultRel)
-		ExecCloseScanRelation(dmstate->resultRel);
-
 	/* MemoryContext will be deleted automatically. */
 }
 
@@ -5756,7 +5752,7 @@ conversion_error_callback(void *arg)
 			RangeTblEntry *rte;
 			Var		   *var = (Var *) tle->expr;
 
-			rte = rt_fetch(var->varno, estate->es_range_table);
+			rte = exec_rt_fetch(var->varno, estate->es_range_table_array);
 
 			if (var->varattno == 0)
 				is_wholerow = true;
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 0c4a7b6f4f..57efb20d20 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2483,7 +2483,8 @@ CopyFrom(CopyState cstate)
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
-	estate->es_range_table = cstate->range_table;
+
+	ExecInitRangeTable(estate, cstate->range_table);
 
 	/* Set up a tuple slot too */
 	myslot = ExecInitExtraTupleSlot(estate, tupDesc);
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 34f9db93d5..f0d45844c1 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -75,7 +75,8 @@ static int	MyTriggerDepth = 0;
  * to be changed, however.
  */
 #define GetUpdatedColumns(relinfo, estate) \
-	(rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->updatedCols)
+	(exec_rt_fetch((relinfo)->ri_RangeTableIndex,\
+				   (estate)->es_range_table_array)->updatedCols)
 
 /* Local function prototypes */
 static void ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid);
diff --git a/src/backend/executor/README b/src/backend/executor/README
index 0d7cd552eb..30df410e0e 100644
--- a/src/backend/executor/README
+++ b/src/backend/executor/README
@@ -267,8 +267,8 @@ This is a sketch of control flow for full query processing:
 
 Per above comments, it's not really critical for ExecEndNode to free any
 memory; it'll all go away in FreeExecutorState anyway.  However, we do need to
-be careful to close relations, drop buffer pins, etc, so we do need to scan
-the plan state tree to find these sorts of resources.
+be careful to drop buffer pins, etc, so we do need to scan the plan state tree
+to find these sorts of resources.
 
 
 The executor can also be used to evaluate simple expressions without any Plan
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index f597363eb1..f8ca60ae5a 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -3934,10 +3934,11 @@ ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 		 * perhaps other places.)
 		 */
 		if (econtext->ecxt_estate &&
-			variable->varno <= list_length(econtext->ecxt_estate->es_range_table))
+			variable->varno <= econtext->ecxt_estate->es_range_table_size)
 		{
-			RangeTblEntry *rte = rt_fetch(variable->varno,
-										  econtext->ecxt_estate->es_range_table);
+			RangeTblEntry *rte =
+				exec_rt_fetch(variable->varno,
+							  econtext->ecxt_estate->es_range_table_array);
 
 			if (rte->eref)
 				ExecTypeSetColNames(output_tupdesc, rte->eref->colnames);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index fe2bbd6b5d..2ca25d857d 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -111,9 +111,9 @@ static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate,
  * to be changed, however.
  */
 #define GetInsertedColumns(relinfo, estate) \
-	(rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->insertedCols)
+	(exec_rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table_array)->insertedCols)
 #define GetUpdatedColumns(relinfo, estate) \
-	(rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->updatedCols)
+	(exec_rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table_array)->updatedCols)
 
 /* end of local decls */
 
@@ -837,26 +837,15 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	 */
 	ExecCheckRTPerms(rangeTable, true);
 
-#ifdef USE_ASSERT_CHECKING
-	foreach(l, rangeTable)
-	{
-		RangeTblEntry *rte = lfirst(l);
-
-		if (rte->rtekind == RTE_RELATION && OidIsValid(rte->relid))
-		{
-			/*
-			 * The following asserts that no new lock needed to be taken,
-			 * meaning the upstream code already acquired the needed locks.
-			 */
-			Assert(rte->lockmode != NoLock);
-			if (!IsParallelWorker() &&
-				!CheckRelationLockedByUs(rte->relid, rte->lockmode))
-				elog(WARNING, "InitPlan: lock on \"%s\" not already taken",
-					 get_rel_name(rte->relid));
-		}
-	}
-#endif
+	ExecInitRangeTable(estate, rangeTable);
 
+	/*
+	 * Allocate an array to store open Relations of each rangeTable item.  We
+	 * 0 fill this for now.  The Relations are only opened and stored here the
+	 * first time they're required during node initialization.
+	 */
+	estate->es_relations = (Relation *) palloc0(estate->es_range_table_size *
+												sizeof(Relation));
 	/*
 	 * initialize the node's execution state
 	 */
@@ -1500,6 +1489,14 @@ static void
 ExecEndPlan(PlanState *planstate, EState *estate)
 {
 	ListCell   *l;
+	int			i;
+
+	/* Close range table relations. */
+	for (i = 0; i < estate->es_range_table_size; i++)
+	{
+		if (estate->es_relations[i])
+			heap_close(estate->es_relations[i], NoLock);
+	}
 
 	/*
 	 * shut down the node-type-specific query processing
@@ -1526,18 +1523,6 @@ ExecEndPlan(PlanState *planstate, EState *estate)
 
 	/* likewise close any trigger target relations */
 	ExecCleanUpTriggerState(estate);
-
-	/*
-	 * close any relations selected FOR [KEY] UPDATE/SHARE, again keeping
-	 * locks
-	 */
-	foreach(l, estate->es_rowMarks)
-	{
-		ExecRowMark *erm = (ExecRowMark *) lfirst(l);
-
-		if (erm->relation)
-			heap_close(erm->relation, NoLock);
-	}
 }
 
 /* ----------------------------------------------------------------
@@ -2294,24 +2279,20 @@ ExecRowMark *
 ExecBuildRowMark(EState *estate, PlanRowMark *rc)
 {
 	ExecRowMark *erm;
-	Oid			relid;
 	Relation	relation;
 
 	Assert(!rc->isParent);
 
-	/* get relation's OID (will produce InvalidOid if subquery) */
-	relid = getrelid(rc->rti, estate->es_range_table);
-
 	switch (rc->markType)
 	{
 		case ROW_MARK_EXCLUSIVE:
 		case ROW_MARK_NOKEYEXCLUSIVE:
 		case ROW_MARK_SHARE:
 		case ROW_MARK_KEYSHARE:
-			relation = heap_open(relid, NoLock);
+			relation = ExecRangeTableRelation(estate, rc->rti);
 			break;
 		case ROW_MARK_REFERENCE:
-			relation = heap_open(relid, NoLock);
+			relation = ExecRangeTableRelation(estate, rc->rti);
 			break;
 		case ROW_MARK_COPY:
 			/* no physical table access is required */
@@ -2329,7 +2310,7 @@ ExecBuildRowMark(EState *estate, PlanRowMark *rc)
 
 	erm = (ExecRowMark *) palloc(sizeof(ExecRowMark));
 	erm->relation = relation;
-	erm->relid = relid;
+	erm->relid = exec_rt_fetch(rc->rti, estate->es_range_table_array)->relid;
 	erm->rti = rc->rti;
 	erm->prti = rc->prti;
 	erm->rowmarkId = rc->rowmarkId;
@@ -2987,7 +2968,7 @@ EvalPlanQualBegin(EPQState *epqstate, EState *parentestate)
 		/*
 		 * We already have a suitable child EPQ tree, so just reset it.
 		 */
-		int			rtsize = list_length(parentestate->es_range_table);
+		int			rtsize = parentestate->es_range_table_size;
 		PlanState  *planstate = epqstate->planstate;
 
 		MemSet(estate->es_epqScanDone, 0, rtsize * sizeof(bool));
@@ -3040,7 +3021,7 @@ EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
 	MemoryContext oldcontext;
 	ListCell   *l;
 
-	rtsize = list_length(parentestate->es_range_table);
+	rtsize = parentestate->es_range_table_size;
 
 	epqstate->estate = estate = CreateExecutorState();
 
@@ -3064,6 +3045,9 @@ EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
 	estate->es_snapshot = parentestate->es_snapshot;
 	estate->es_crosscheck_snapshot = parentestate->es_crosscheck_snapshot;
 	estate->es_range_table = parentestate->es_range_table;
+	estate->es_range_table_array = parentestate->es_range_table_array;
+	estate->es_range_table_size = parentestate->es_range_table_size;
+	estate->es_relations = parentestate->es_relations;
 	estate->es_plannedstmt = parentestate->es_plannedstmt;
 	estate->es_junkFilter = parentestate->es_junkFilter;
 	estate->es_output_cid = parentestate->es_output_cid;
@@ -3217,18 +3201,6 @@ EvalPlanQualEnd(EPQState *epqstate)
 		ExecEndNode(subplanstate);
 	}
 
-	/*
-	 * close any relations selected FOR [KEY] UPDATE/SHARE, again keeping
-	 * locks
-	 */
-	foreach(l, estate->es_rowMarks)
-	{
-		ExecRowMark *erm = (ExecRowMark *) lfirst(l);
-
-		if (erm->relation)
-			heap_close(erm->relation, NoLock);
-	}
-
 	/* throw away the per-estate tuple table */
 	ExecResetTupleTable(estate->es_tupleTable, false);
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 1d2b3620ee..7fb34d2173 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -861,11 +861,10 @@ ExecCleanupTupleRouting(ModifyTableState *mtstate,
 	int			subplan_index = 0;
 
 	/*
-	 * Remember, proute->partition_dispatch_info[0] corresponds to the root
-	 * partitioned table, which we must not try to close, because it is the
-	 * main target table of the query that will be closed by callers such as
-	 * ExecEndPlan() or DoCopy(). Also, tupslot is NULL for the root
-	 * partitioned table.
+	 * proute->partition_dispatch_info[0] corresponds to the root partitioned
+	 * table, which is the main target table of the query, so it is closed by
+	 * ExecEndModifyTable() or DoCopy(). Also, tupslot is NULL for the root
+	 * partitioned table, so nothing to do here for the root table.
 	 */
 	for (i = 1; i < proute->num_dispatch; i++)
 	{
@@ -1418,9 +1417,6 @@ adjust_partition_tlist(List *tlist, TupleConversionMap *map)
  *		functions.  Details stored include how to map the partition index
  *		returned by the partition pruning code into subplan indexes.
  *
- * ExecDestroyPartitionPruneState:
- *		Deletes a PartitionPruneState. Must be called during executor shutdown.
- *
  * ExecFindInitialMatchingSubPlans:
  *		Returns indexes of matching subplans.  Partition pruning is attempted
  *		without any evaluation of expressions containing PARAM_EXEC Params.
@@ -1462,6 +1458,7 @@ PartitionPruneState *
 ExecCreatePartitionPruneState(PlanState *planstate,
 							  PartitionPruneInfo *partitionpruneinfo)
 {
+	EState	   *estate = planstate->state;
 	PartitionPruneState *prunestate;
 	int			n_part_hierarchies;
 	ListCell   *lc;
@@ -1542,7 +1539,7 @@ ExecCreatePartitionPruneState(PlanState *planstate,
 			 * so that we can rely on its copies of the table's partition key
 			 * and partition descriptor.
 			 */
-			context->partrel = relation_open(pinfo->reloid, NoLock);
+			context->partrel = ExecRangeTableRelation(estate, pinfo->rtindex);
 
 			partkey = RelationGetPartitionKey(context->partrel);
 			partdesc = RelationGetPartitionDesc(context->partrel);
@@ -1622,30 +1619,6 @@ ExecCreatePartitionPruneState(PlanState *planstate,
 	return prunestate;
 }
 
-/*
- * ExecDestroyPartitionPruneState
- *		Release resources at plan shutdown.
- *
- * We don't bother to free any memory here, since the whole executor context
- * will be going away shortly.  We do need to release our relcache pins.
- */
-void
-ExecDestroyPartitionPruneState(PartitionPruneState *prunestate)
-{
-	PartitionPruningData **partprunedata = prunestate->partprunedata;
-	int			i;
-
-	for (i = 0; i < prunestate->num_partprunedata; i++)
-	{
-		PartitionPruningData *prunedata = partprunedata[i];
-		PartitionedRelPruningData *pprune = prunedata->partrelprunedata;
-		int			j;
-
-		for (j = 0; j < prunedata->num_partrelprunedata; j++)
-			relation_close(pprune[j].context.partrel, NoLock);
-	}
-}
-
 /*
  * ExecFindInitialMatchingSubPlans
  *		Identify the set of subplans that cannot be eliminated by initial
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index d32796aac3..ab46fd425d 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -25,7 +25,6 @@
  *		etc
  *
  *		ExecOpenScanRelation	Common code for scan node init routines.
- *		ExecCloseScanRelation
  *
  *		executor_errposition	Report syntactic position of an error.
  *
@@ -35,6 +34,8 @@
  *		GetAttributeByName		Runtime extraction of columns from tuples.
  *		GetAttributeByNum
  *
+ *		ExecInitRangeTable		Build an array from the range table list to
+ *								allow faster lookups by relid.
  *	 NOTES
  *		This file has traditionally been the place to stick misc.
  *		executor support stuff that doesn't really go anyplace else.
@@ -653,11 +654,9 @@ Relation
 ExecOpenScanRelation(EState *estate, Index scanrelid, int eflags)
 {
 	Relation	rel;
-	Oid			reloid;
 
 	/* Open the relation. */
-	reloid = getrelid(scanrelid, estate->es_range_table);
-	rel = heap_open(reloid, NoLock);
+	rel = ExecRangeTableRelation(estate, scanrelid);
 
 	/*
 	 * Complain if we're attempting a scan of an unscannable relation, except
@@ -675,26 +674,6 @@ ExecOpenScanRelation(EState *estate, Index scanrelid, int eflags)
 	return rel;
 }
 
-/* ----------------------------------------------------------------
- *		ExecCloseScanRelation
- *
- *		Close the heap relation scanned by a base-level scan plan node.
- *		This should be called during the node's ExecEnd routine.
- *
- * Currently, we do not release the lock acquired by ExecOpenScanRelation.
- * This lock should be held till end of transaction.  (There is a faction
- * that considers this too much locking, however.)
- *
- * If we did want to release the lock, we'd have to repeat the logic in
- * ExecOpenScanRelation in order to figure out what to release.
- * ----------------------------------------------------------------
- */
-void
-ExecCloseScanRelation(Relation scanrel)
-{
-	heap_close(scanrel, NoLock);
-}
-
 /*
  * UpdateChangedParamSet
  *		Add changed parameters to a plan node's chgParam set
@@ -997,3 +976,43 @@ ExecCleanTargetListLength(List *targetlist)
 	}
 	return len;
 }
+
+/*
+ * Initialize executor's range table array
+ */
+void
+ExecInitRangeTable(EState *estate, List *rangeTable)
+{
+	int		rti;
+	ListCell *l;
+
+#ifdef USE_ASSERT_CHECKING
+	foreach(l, rangeTable)
+	{
+		RangeTblEntry *rte = lfirst(l);
+
+		if (rte->rtekind == RTE_RELATION && OidIsValid(rte->relid))
+		{
+			/*
+			 * The following asserts that no new lock needed to be taken,
+			 * meaning the upstream code already acquired the needed locks.
+			 */
+			Assert(rte->lockmode != NoLock);
+			if (!IsParallelWorker() &&
+				!CheckRelationLockedByUs(rte->relid, rte->lockmode))
+				elog(WARNING, "InitPlan: lock on \"%s\" not already taken",
+					get_rel_name(rte->relid));
+		}
+	}
+#endif
+
+	Assert(estate != NULL);
+	estate->es_range_table = rangeTable;
+	estate->es_range_table_size = list_length(rangeTable);
+	estate->es_range_table_array = (RangeTblEntry **)
+										palloc(estate->es_range_table_size *
+											   sizeof(RangeTblEntry *));
+	rti = 0;
+	foreach(l, rangeTable)
+		estate->es_range_table_array[rti++] = lfirst_node(RangeTblEntry, l);
+}
diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c
index eb587be221..a16b6da474 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -329,12 +329,6 @@ ExecEndAppend(AppendState *node)
 	 */
 	for (i = 0; i < nplans; i++)
 		ExecEndNode(appendplans[i]);
-
-	/*
-	 * release any resources associated with run-time pruning
-	 */
-	if (node->as_prune_state)
-		ExecDestroyPartitionPruneState(node->as_prune_state);
 }
 
 void
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index baffae27e3..5307cd1b87 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -785,13 +785,11 @@ ExecReScanBitmapHeapScan(BitmapHeapScanState *node)
 void
 ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 {
-	Relation	relation;
 	HeapScanDesc scanDesc;
 
 	/*
 	 * extract information from the node
 	 */
-	relation = node->ss.ss_currentRelation;
 	scanDesc = node->ss.ss_currentScanDesc;
 
 	/*
@@ -832,11 +830,6 @@ ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 	 * close heap scan
 	 */
 	heap_endscan(scanDesc);
-
-	/*
-	 * close the heap relation.
-	 */
-	ExecCloseScanRelation(relation);
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c
index b816e0b31d..9a33eda688 100644
--- a/src/backend/executor/nodeCustom.c
+++ b/src/backend/executor/nodeCustom.c
@@ -126,10 +126,6 @@ ExecEndCustomScan(CustomScanState *node)
 	/* Clean out the tuple table */
 	ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
 	ExecClearTuple(node->ss.ss_ScanTupleSlot);
-
-	/* Close the heap relation */
-	if (node->ss.ss_currentRelation)
-		ExecCloseScanRelation(node->ss.ss_currentRelation);
 }
 
 void
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index a2a28b7ec2..cf7df72d8c 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -258,10 +258,6 @@ ExecEndForeignScan(ForeignScanState *node)
 	/* clean out the tuple table */
 	ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
 	ExecClearTuple(node->ss.ss_ScanTupleSlot);
-
-	/* close the relation. */
-	if (node->ss.ss_currentRelation)
-		ExecCloseScanRelation(node->ss.ss_currentRelation);
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index 4b6d531810..1b530cea40 100644
--- a/src/backend/executor/nodeIndexonlyscan.c
+++ b/src/backend/executor/nodeIndexonlyscan.c
@@ -373,14 +373,12 @@ ExecEndIndexOnlyScan(IndexOnlyScanState *node)
 {
 	Relation	indexRelationDesc;
 	IndexScanDesc indexScanDesc;
-	Relation	relation;
 
 	/*
 	 * extract information from the node
 	 */
 	indexRelationDesc = node->ioss_RelationDesc;
 	indexScanDesc = node->ioss_ScanDesc;
-	relation = node->ss.ss_currentRelation;
 
 	/* Release VM buffer pin, if any. */
 	if (node->ioss_VMBuffer != InvalidBuffer)
@@ -411,11 +409,6 @@ ExecEndIndexOnlyScan(IndexOnlyScanState *node)
 		index_endscan(indexScanDesc);
 	if (indexRelationDesc)
 		index_close(indexRelationDesc, NoLock);
-
-	/*
-	 * close the heap relation.
-	 */
-	ExecCloseScanRelation(relation);
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 6285a2114e..786e7ac25c 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -802,14 +802,12 @@ ExecEndIndexScan(IndexScanState *node)
 {
 	Relation	indexRelationDesc;
 	IndexScanDesc indexScanDesc;
-	Relation	relation;
 
 	/*
 	 * extract information from the node
 	 */
 	indexRelationDesc = node->iss_RelationDesc;
 	indexScanDesc = node->iss_ScanDesc;
-	relation = node->ss.ss_currentRelation;
 
 	/*
 	 * Free the exprcontext(s) ... now dead code, see ExecFreeExprContext
@@ -833,11 +831,6 @@ ExecEndIndexScan(IndexScanState *node)
 		index_endscan(indexScanDesc);
 	if (indexRelationDesc)
 		index_close(indexRelationDesc, NoLock);
-
-	/*
-	 * close the heap relation.
-	 */
-	ExecCloseScanRelation(relation);
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c
index efbf4bd18a..ff9606342b 100644
--- a/src/backend/executor/nodeLockRows.c
+++ b/src/backend/executor/nodeLockRows.c
@@ -400,7 +400,7 @@ ExecInitLockRows(LockRows *node, EState *estate, int eflags)
 	/*
 	 * Create workspace in which we can remember per-RTE locked tuples
 	 */
-	lrstate->lr_ntables = list_length(estate->es_range_table);
+	lrstate->lr_ntables = estate->es_range_table_size;
 	lrstate->lr_curtuples = (HeapTuple *)
 		palloc0(lrstate->lr_ntables * sizeof(HeapTuple));
 
diff --git a/src/backend/executor/nodeMergeAppend.c b/src/backend/executor/nodeMergeAppend.c
index d8e0978966..5d0dbb8146 100644
--- a/src/backend/executor/nodeMergeAppend.c
+++ b/src/backend/executor/nodeMergeAppend.c
@@ -363,12 +363,6 @@ ExecEndMergeAppend(MergeAppendState *node)
 	 */
 	for (i = 0; i < nplans; i++)
 		ExecEndNode(mergeplans[i]);
-
-	/*
-	 * release any resources associated with run-time pruning
-	 */
-	if (node->ms_prune_state)
-		ExecDestroyPartitionPruneState(node->ms_prune_state);
 }
 
 void
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index b18f9e3164..c837d36a87 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -2243,10 +2243,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	foreach(l, node->resultRelations)
 	{
 		Index		resultRelationIndex = lfirst_int(l);
-		RangeTblEntry *rte = rt_fetch(resultRelationIndex,
-									  estate->es_range_table);
-		Relation	rel = heap_open(rte->relid, NoLock);
+		Relation	rel;
 
+		rel = ExecRangeTableRelation(estate, resultRelationIndex);
 		InitResultRelInfo(resultRelInfo, rel, resultRelationIndex, NULL,
 						  estate->es_instrument);
 		resultRelInfo++;
@@ -2255,17 +2254,18 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	/* If modifying a partitioned table, initialize the root table info */
 	if (node->rootResultRelIndex >= 0)
 	{
-		RangeTblEntry  *rte;
 		Relation		rel;
 
 		mtstate->rootResultRelInfo = estate->es_root_result_relations +
 			node->rootResultRelIndex;
 
 		Assert(node->partitionedTarget);
-		rte = rt_fetch(node->targetRelation, estate->es_range_table);
-		rel = heap_open(rte->relid, NoLock);
-		InitResultRelInfo(mtstate->rootResultRelInfo, rel,
-						  node->targetRelation, NULL, estate->es_instrument);
+		rel = ExecRangeTableRelation(estate, node->targetRelation);
+		InitResultRelInfo(mtstate->rootResultRelInfo,
+						  rel,
+						  node->targetRelation,
+						  NULL,
+						  estate->es_instrument);
 	}
 
 	mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
@@ -2722,15 +2722,13 @@ ExecEndModifyTable(ModifyTableState *node)
 			resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
 														   resultRelInfo);
 
-		/* Close indices and then the relation itself */
+		/*
+		 * Close the ResultRelInfo's indexes. The relation itself
+		 * is handled by the executor cleanup code.
+		 */
 		ExecCloseIndices(resultRelInfo);
-		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
 	}
 
-	/* Close the root partitioned table, if any. */
-	if (node->rootResultRelInfo)
-		heap_close(node->rootResultRelInfo->ri_RelationDesc, NoLock);
-
 	/* Close all the partitioned tables, leaf partitions, and their indices */
 	if (node->mt_partition_tuple_routing)
 		ExecCleanupTupleRouting(node, node->mt_partition_tuple_routing);
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index 99528be84a..92ff68a764 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -222,11 +222,6 @@ ExecEndSampleScan(SampleScanState *node)
 	 */
 	if (node->ss.ss_currentScanDesc)
 		heap_endscan(node->ss.ss_currentScanDesc);
-
-	/*
-	 * close the heap relation.
-	 */
-	ExecCloseScanRelation(node->ss.ss_currentRelation);
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index cd53491be0..92ff1e50ed 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -201,13 +201,11 @@ ExecInitSeqScan(SeqScan *node, EState *estate, int eflags)
 void
 ExecEndSeqScan(SeqScanState *node)
 {
-	Relation	relation;
 	HeapScanDesc scanDesc;
 
 	/*
 	 * get information from node
 	 */
-	relation = node->ss.ss_currentRelation;
 	scanDesc = node->ss.ss_currentScanDesc;
 
 	/*
@@ -226,11 +224,6 @@ ExecEndSeqScan(SeqScanState *node)
 	 */
 	if (scanDesc != NULL)
 		heap_endscan(scanDesc);
-
-	/*
-	 * close the heap relation.
-	 */
-	ExecCloseScanRelation(relation);
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
index 0cb1946a3e..8cae56f48c 100644
--- a/src/backend/executor/nodeTidscan.c
+++ b/src/backend/executor/nodeTidscan.c
@@ -489,11 +489,6 @@ ExecEndTidScan(TidScanState *node)
 	 */
 	ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
 	ExecClearTuple(node->ss.ss_ScanTupleSlot);
-
-	/*
-	 * close the heap relation.
-	 */
-	ExecCloseScanRelation(node->ss.ss_currentRelation);
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da02b8961c..dd76284676 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1190,7 +1190,7 @@ _copyPartitionedRelPruneInfo(const PartitionedRelPruneInfo *from)
 {
 	PartitionedRelPruneInfo *newnode = makeNode(PartitionedRelPruneInfo);
 
-	COPY_SCALAR_FIELD(reloid);
+	COPY_SCALAR_FIELD(rtindex);
 	COPY_NODE_FIELD(pruning_steps);
 	COPY_BITMAPSET_FIELD(present_parts);
 	COPY_SCALAR_FIELD(nparts);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 2e6a5a1b42..6aefdd8bc4 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1029,7 +1029,7 @@ _outPartitionedRelPruneInfo(StringInfo str, const PartitionedRelPruneInfo *node)
 
 	WRITE_NODE_TYPE("PARTITIONEDRELPRUNEINFO");
 
-	WRITE_OID_FIELD(reloid);
+	WRITE_UINT_FIELD(rtindex);
 	WRITE_NODE_FIELD(pruning_steps);
 	WRITE_BITMAPSET_FIELD(present_parts);
 	WRITE_INT_FIELD(nparts);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index aea5b0abc4..30609c96e6 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2373,7 +2373,7 @@ _readPartitionedRelPruneInfo(void)
 {
 	READ_LOCALS(PartitionedRelPruneInfo);
 
-	READ_OID_FIELD(reloid);
+	READ_UINT_FIELD(rtindex);
 	READ_NODE_FIELD(pruning_steps);
 	READ_BITMAPSET_FIELD(present_parts);
 	READ_INT_FIELD(nparts);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 17d53f356c..ee01ef82d2 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -890,6 +890,21 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 											  (Plan *) lfirst(l),
 											  rtoffset);
 				}
+				if (splan->part_prune_info)
+				{
+					foreach(l, splan->part_prune_info->prune_infos)
+					{
+						List *prune_infos = lfirst(l);
+						ListCell *l2;
+
+						foreach(l2, prune_infos)
+						{
+							PartitionedRelPruneInfo *pinfo = lfirst(l2);
+
+							pinfo->rtindex += rtoffset;
+						}
+					}
+				}
 			}
 			break;
 		case T_MergeAppend:
@@ -908,6 +923,21 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 											  (Plan *) lfirst(l),
 											  rtoffset);
 				}
+				if (splan->part_prune_info)
+				{
+					foreach(l, splan->part_prune_info->prune_infos)
+					{
+						List *prune_infos = lfirst(l);
+						ListCell *l2;
+
+						foreach(l2, prune_infos)
+						{
+							PartitionedRelPruneInfo *pinfo = lfirst(l2);
+
+							pinfo->rtindex += rtoffset;
+						}
+					}
+				}
 			}
 			break;
 		case T_RecursiveUnion:
diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c
index e1ce8b4ddc..1763dd67a3 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -357,7 +357,6 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
 		Index		rti = lfirst_int(lc);
 		RelOptInfo *subpart = find_base_rel(root, rti);
 		PartitionedRelPruneInfo *pinfo;
-		RangeTblEntry *rte;
 		Bitmapset  *present_parts;
 		int			nparts = subpart->nparts;
 		int			partnatts = subpart->part_scheme->partnatts;
@@ -459,10 +458,8 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
 				present_parts = bms_add_member(present_parts, i);
 		}
 
-		rte = root->simple_rte_array[subpart->relid];
-
 		pinfo = makeNode(PartitionedRelPruneInfo);
-		pinfo->reloid = rte->relid;
+		pinfo->rtindex = rti;
 		pinfo->pruning_steps = pruning_steps;
 		pinfo->present_parts = present_parts;
 		pinfo->nparts = nparts;
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 905a983074..d72f6a1b4f 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -795,7 +795,14 @@ copy_table(Relation rel)
 	copybuf = makeStringInfo();
 
 	pstate = make_parsestate(NULL);
-	addRangeTableEntryForRelation(pstate, rel, NULL, false, false, NoLock);
+
+	/*
+	 * Caller LogicalRepSyncTableStart took RowExclusiveLock, which we must
+	 * record in the RTE being built, because executor initialization invoked
+	 * by copy.c expects it.
+	 */
+	addRangeTableEntryForRelation(pstate, rel, NULL, false, false,
+								  RowExclusiveLock);
 
 	attnamelist = make_copy_attnamelist(relmapentry);
 	cstate = BeginCopyFrom(pstate, rel, NULL, false, copy_read_data, attnamelist, NIL);
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 42345f94d3..c63de66857 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -200,6 +200,8 @@ create_estate_for_relation(LogicalRepRelMapEntry *rel)
 	rte->relid = RelationGetRelid(rel->localrel);
 	rte->relkind = rel->localrel->rd_rel->relkind;
 	estate->es_range_table = list_make1(rte);
+	estate->es_range_table_array = &rte;
+	estate->es_range_table_size = 1;
 
 	resultRelInfo = makeNode(ResultRelInfo);
 	InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
diff --git a/src/include/executor/execPartition.h b/src/include/executor/execPartition.h
index 89ce53815c..04847d639d 100644
--- a/src/include/executor/execPartition.h
+++ b/src/include/executor/execPartition.h
@@ -197,7 +197,6 @@ extern void ExecCleanupTupleRouting(ModifyTableState *mtstate,
 						PartitionTupleRouting *proute);
 extern PartitionPruneState *ExecCreatePartitionPruneState(PlanState *planstate,
 							  PartitionPruneInfo *partitionpruneinfo);
-extern void ExecDestroyPartitionPruneState(PartitionPruneState *prunestate);
 extern Bitmapset *ExecFindMatchingSubPlans(PartitionPruneState *prunestate);
 extern Bitmapset *ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate,
 								int nsubplans);
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 1d2b48fe09..b03d8c591f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -17,6 +17,7 @@
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
 #include "utils/memutils.h"
+#include "utils/rel.h"
 
 
 /*
@@ -478,6 +479,7 @@ extern ExprContext *CreateExprContext(EState *estate);
 extern ExprContext *CreateStandaloneExprContext(void);
 extern void FreeExprContext(ExprContext *econtext, bool isCommit);
 extern void ReScanExprContext(ExprContext *econtext);
+extern void ExecInitRangeTable(EState *estate, List *rangeTable);
 
 #define ResetExprContext(econtext) \
 	MemoryContextReset((econtext)->ecxt_per_tuple_memory)
@@ -513,7 +515,6 @@ extern void ExecCreateScanSlotFromOuterPlan(EState *estate, ScanState *scanstate
 extern bool ExecRelationIsTargetRelation(EState *estate, Index scanrelid);
 
 extern Relation ExecOpenScanRelation(EState *estate, Index scanrelid, int eflags);
-extern void ExecCloseScanRelation(Relation scanrel);
 
 extern int	executor_errposition(EState *estate, int location);
 
@@ -568,4 +569,25 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
 extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
 						 const char *relname);
 
+#ifndef FRONTEND
+static inline Relation
+ExecRangeTableRelation(EState *estate, Index rti)
+{
+	Relation rel = estate->es_relations[rti - 1];
+
+	if (rel == NULL)
+	{
+		RangeTblEntry *rte = exec_rt_fetch(rti, estate->es_range_table_array);
+
+		/*
+		 * No need to lock the relation.  Locks were already
+		 * obtained by the upstream code.
+		 */
+		rel = estate->es_relations[rti - 1] = heap_open(rte->relid, NoLock);
+	}
+
+	return rel;
+}
+#endif
+
 #endif							/* EXECUTOR_H  */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 03ad516976..639235d622 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -20,6 +20,7 @@
 #include "executor/instrument.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
+#include "nodes/parsenodes.h"
 #include "nodes/plannodes.h"
 #include "utils/hsearch.h"
 #include "utils/queryenvironment.h"
@@ -478,7 +479,11 @@ typedef struct EState
 	ScanDirection es_direction; /* current scan direction */
 	Snapshot	es_snapshot;	/* time qual to use */
 	Snapshot	es_crosscheck_snapshot; /* crosscheck time qual for RI */
-	List	   *es_range_table; /* List of RangeTblEntry */
+	List	   *es_range_table;	/* List of RangeTblEntry */
+	struct RangeTblEntry **es_range_table_array;	/* 0-based range table */
+	int			es_range_table_size;	/* size of the range table array */
+	Relation   *es_relations;	/* 0-based array of Relations
+								 * es_range_table_size elements in length. */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
@@ -579,6 +584,22 @@ typedef struct EState
 	struct JitInstrumentation *es_jit_combined_instr;
 } EState;
 
+/*
+ *		exec_rt_fetch
+ *
+ * NB: this will crash and burn if handed an out-of-range RT index
+ */
+#define exec_rt_fetch(rangetblidx, rangetbl) rangetbl[(rangetblidx) - 1]
+
+/*
+ *		getrelid
+ *
+ *		Given the range index of a relation, return the corresponding
+ *		relation OID.  Note that InvalidOid will be returned if the
+ *		RTE is for a non-relation-type RTE.
+ */
+#define getrelid(rangeindex,rangetable) \
+	(exec_rt_fetch(rangeindex, rangetable)->relid)
 
 /*
  * ExecRowMark -
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 27ae73e3d4..452627dd03 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -1093,7 +1093,7 @@ typedef struct PartitionPruneInfo
 typedef struct PartitionedRelPruneInfo
 {
 	NodeTag		type;
-	Oid			reloid;			/* OID of partition rel for this level */
+	Index		rtindex;		/* Partition table's RT index */
 	List	   *pruning_steps;	/* List of PartitionPruneStep, see below */
 	Bitmapset  *present_parts;	/* Indexes of all partitions which subplans or
 								 * subparts are present for. */
diff --git a/src/include/parser/parsetree.h b/src/include/parser/parsetree.h
index dd9ae658ac..fe16d7d1fa 100644
--- a/src/include/parser/parsetree.h
+++ b/src/include/parser/parsetree.h
@@ -31,16 +31,6 @@
 #define rt_fetch(rangetable_index, rangetable) \
 	((RangeTblEntry *) list_nth(rangetable, (rangetable_index)-1))
 
-/*
- *		getrelid
- *
- *		Given the range index of a relation, return the corresponding
- *		relation OID.  Note that InvalidOid will be returned if the
- *		RTE is for a non-relation-type RTE.
- */
-#define getrelid(rangeindex,rangetable) \
-	(rt_fetch(rangeindex, rangetable)->relid)
-
 /*
  * Given an RTE and an attribute number, return the appropriate
  * variable name or alias for that attribute of that RTE.
-- 
2.16.2.windows.1

