resultcache_in_nestloop_hacks.patch.txt

text/plain

Filename: resultcache_in_nestloop_hacks.patch.txt
Type: text/plain
Part: 0
Message: Re: Hybrid Hash/Nested Loop joins and caching results from subplans
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 6d4b9eb3b9..42c6df549f 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -108,8 +108,7 @@ static void show_sort_info(SortState *sortstate, ExplainState *es);
 static void show_incremental_sort_info(IncrementalSortState *incrsortstate,
 									   ExplainState *es);
 static void show_hash_info(HashState *hashstate, ExplainState *es);
-static void show_resultcache_info(ResultCacheState *rcstate, List *ancestors,
-								  ExplainState *es);
+static void show_resultcache_info(NestLoopState *nlstate, List *ancestors, ExplainState *es);
 static void show_hashagg_info(AggState *hashstate, ExplainState *es);
 static void show_tidbitmap_info(BitmapHeapScanState *planstate,
 								ExplainState *es);
@@ -1494,10 +1493,13 @@ ExplainNode(PlanState *planstate, List *ancestors,
 					 * For historical reasons, the join type is interpolated
 					 * into the node type name...
 					 */
-					if (((Join *) plan)->jointype != JOIN_INNER)
+					if (((Join *)plan)->jointype != JOIN_INNER)
 						appendStringInfo(es->str, " %s Join", jointype);
 					else if (!IsA(plan, NestLoop))
 						appendStringInfoString(es->str, " Join");
+					else if (castNode(NestLoop, plan)->paramcache)
+						appendStringInfoString(es->str, " Cached");
+
 				}
 				else
 					ExplainPropertyText("Join Type", jointype, es);
@@ -1883,6 +1885,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			}
 			break;
 		case T_NestLoop:
+			show_resultcache_info((NestLoopState *) planstate, ancestors, es);
 			show_upper_qual(((NestLoop *) plan)->join.joinqual,
 							"Join Filter", planstate, ancestors, es);
 			if (((NestLoop *) plan)->join.joinqual)
@@ -1963,10 +1966,10 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_Hash:
 			show_hash_info(castNode(HashState, planstate), es);
 			break;
-		case T_ResultCache:
-			show_resultcache_info(castNode(ResultCacheState, planstate),
-								  ancestors, es);
-			break;
+		//case T_ResultCache:
+		//	show_resultcache_info(castNode(ResultCacheState, planstate),
+		//						  ancestors, es);
+		//	break;
 		default:
 			break;
 	}
@@ -3041,15 +3044,19 @@ show_hash_info(HashState *hashstate, ExplainState *es)
 }
 
 static void
-show_resultcache_info(ResultCacheState *rcstate, List *ancestors, ExplainState *es)
+show_resultcache_info(NestLoopState *nlstate, List *ancestors, ExplainState *es)
 {
-	Plan	   *plan = ((PlanState *) rcstate)->plan;
+	Plan	   *plan = ((PlanState *) nlstate)->plan;
+	ResultCacheState *rcstate;
 	ListCell   *lc;
 	List	   *context;
 	StringInfoData keystr;
 	char	   *seperator = "";
 	bool		useprefix;
 
+	if (nlstate->nl_pcache == NULL)
+		return;
+
 	initStringInfo(&keystr);
 
 	/* XXX surely we'll always have more than one if we have a resultcache? */
@@ -3060,7 +3067,7 @@ show_resultcache_info(ResultCacheState *rcstate, List *ancestors, ExplainState *
 									   plan,
 									   ancestors);
 
-	foreach(lc, ((ResultCache *) plan)->param_exprs)
+	foreach(lc, ((NestLoop *) plan)->param_exprs)
 	{
 		Node	   *expr = (Node *) lfirst(lc);
 
@@ -3086,6 +3093,8 @@ show_resultcache_info(ResultCacheState *rcstate, List *ancestors, ExplainState *
 	if (!es->analyze)
 		return;
 
+
+	rcstate = nlstate->nl_pcache;
 	if (es->format != EXPLAIN_FORMAT_TEXT)
 	{
 		ExplainPropertyInteger("Cache Hits", NULL, rcstate->stats.cache_hits, es);
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index 68920ecd89..f9c2f80c79 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -44,7 +44,7 @@
 #include "executor/nodeProjectSet.h"
 #include "executor/nodeRecursiveunion.h"
 #include "executor/nodeResult.h"
-#include "executor/nodeResultCache.h"
+//#include "executor/nodeResultCache.h"
 #include "executor/nodeSamplescan.h"
 #include "executor/nodeSeqscan.h"
 #include "executor/nodeSetOp.h"
@@ -250,9 +250,9 @@ ExecReScan(PlanState *node)
 			ExecReScanMaterial((MaterialState *) node);
 			break;
 
-		case T_ResultCacheState:
-			ExecReScanResultCache((ResultCacheState *) node);
-			break;
+		//case T_ResultCacheState:
+		//	ExecReScanResultCache((ResultCacheState *) node);
+		//	break;
 
 		case T_SortState:
 			ExecReScanSort((SortState *) node);
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 459e9dd3e9..37cfa36881 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -35,7 +35,7 @@
 #include "executor/nodeIncrementalSort.h"
 #include "executor/nodeIndexonlyscan.h"
 #include "executor/nodeIndexscan.h"
-#include "executor/nodeResultCache.h"
+//#include "executor/nodeResultCache.h"
 #include "executor/nodeSeqscan.h"
 #include "executor/nodeSort.h"
 #include "executor/nodeSubplan.h"
@@ -294,10 +294,10 @@ ExecParallelEstimate(PlanState *planstate, ExecParallelEstimateContext *e)
 			/* even when not parallel-aware, for EXPLAIN ANALYZE */
 			ExecAggEstimate((AggState *) planstate, e->pcxt);
 			break;
-		case T_ResultCacheState:
-			/* even when not parallel-aware, for EXPLAIN ANALYZE */
-			ExecResultCacheEstimate((ResultCacheState *) planstate, e->pcxt);
-			break;
+		//case T_ResultCacheState:
+		//	/* even when not parallel-aware, for EXPLAIN ANALYZE */
+		//	ExecResultCacheEstimate((ResultCacheState *) planstate, e->pcxt);
+		//	break;
 		default:
 			break;
 	}
@@ -518,10 +518,10 @@ ExecParallelInitializeDSM(PlanState *planstate,
 			/* even when not parallel-aware, for EXPLAIN ANALYZE */
 			ExecAggInitializeDSM((AggState *) planstate, d->pcxt);
 			break;
-		case T_ResultCacheState:
-			/* even when not parallel-aware, for EXPLAIN ANALYZE */
-			ExecResultCacheInitializeDSM((ResultCacheState *) planstate, d->pcxt);
-			break;
+		//case T_ResultCacheState:
+		//	/* even when not parallel-aware, for EXPLAIN ANALYZE */
+		//	ExecResultCacheInitializeDSM((ResultCacheState *) planstate, d->pcxt);
+		//	break;
 		default:
 			break;
 	}
@@ -998,9 +998,9 @@ ExecParallelReInitializeDSM(PlanState *planstate,
 		case T_HashState:
 		case T_SortState:
 		case T_IncrementalSortState:
-		case T_ResultCacheState:
-			/* these nodes have DSM state, but no reinitialization is required */
-			break;
+		//case T_ResultCacheState:
+		//	/* these nodes have DSM state, but no reinitialization is required */
+		//	break;
 
 		default:
 			break;
@@ -1068,9 +1068,9 @@ ExecParallelRetrieveInstrumentation(PlanState *planstate,
 		case T_AggState:
 			ExecAggRetrieveInstrumentation((AggState *) planstate);
 			break;
-		case T_ResultCacheState:
-			ExecResultCacheRetrieveInstrumentation((ResultCacheState *) planstate);
-			break;
+		//case T_ResultCacheState:
+		//	ExecResultCacheRetrieveInstrumentation((ResultCacheState *) planstate);
+		//	break;
 		default:
 			break;
 	}
@@ -1363,11 +1363,11 @@ ExecParallelInitializeWorker(PlanState *planstate, ParallelWorkerContext *pwcxt)
 			/* even when not parallel-aware, for EXPLAIN ANALYZE */
 			ExecAggInitializeWorker((AggState *) planstate, pwcxt);
 			break;
-		case T_ResultCacheState:
-			/* even when not parallel-aware, for EXPLAIN ANALYZE */
-			ExecResultCacheInitializeWorker((ResultCacheState *) planstate,
-											pwcxt);
-			break;
+		//case T_ResultCacheState:
+		//	/* even when not parallel-aware, for EXPLAIN ANALYZE */
+		//	ExecResultCacheInitializeWorker((ResultCacheState *) planstate,
+		//									pwcxt);
+		//	break;
 		default:
 			break;
 	}
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index fbbe667cc1..e5b8c74da7 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -102,7 +102,7 @@
 #include "executor/nodeProjectSet.h"
 #include "executor/nodeRecursiveunion.h"
 #include "executor/nodeResult.h"
-#include "executor/nodeResultCache.h"
+//#include "executor/nodeResultCache.h"
 #include "executor/nodeSamplescan.h"
 #include "executor/nodeSeqscan.h"
 #include "executor/nodeSetOp.h"
@@ -320,10 +320,10 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 														   estate, eflags);
 			break;
 
-		case T_ResultCache:
-			result = (PlanState *) ExecInitResultCache((ResultCache *) node,
-													   estate, eflags);
-			break;
+		//case T_ResultCache:
+		//	result = (PlanState *) ExecInitResultCache((ResultCache *) node,
+		//											   estate, eflags);
+		//	break;
 
 		case T_Group:
 			result = (PlanState *) ExecInitGroup((Group *) node,
@@ -709,9 +709,9 @@ ExecEndNode(PlanState *node)
 			ExecEndIncrementalSort((IncrementalSortState *) node);
 			break;
 
-		case T_ResultCacheState:
-			ExecEndResultCache((ResultCacheState *) node);
-			break;
+		//case T_ResultCacheState:
+		//	ExecEndResultCache((ResultCacheState *) node);
+		//	break;
 
 		case T_GroupState:
 			ExecEndGroup((GroupState *) node);
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index b07c2996d4..97213071d5 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -23,9 +23,21 @@
 
 #include "executor/execdebug.h"
 #include "executor/nodeNestloop.h"
+#include "executor/nodeResultCache.h"
 #include "miscadmin.h"
 #include "utils/memutils.h"
 
+static inline TupleTableSlot *
+FetchInnerTuple(ResultCacheState *rcstate, PlanState *innerPlan)
+{
+	/* No caching? Just exec the inner node */
+	if (rcstate == NULL)
+		return ExecProcNode(innerPlan);
+	/* Otherwise let the cache deal with it */
+	else
+		return ExecResultCache(rcstate, innerPlan);
+}
+
 
 /* ----------------------------------------------------------------
  *		ExecNestLoop(node)
@@ -150,6 +162,11 @@ ExecNestLoop(PlanState *pstate)
 			 */
 			ENL1_printf("rescanning inner plan");
 			ExecReScan(innerPlan);
+
+			/* When using a result cache, reset the state ready for another lookup */
+			if (node->nl_pcache)
+				ExecResultCacheFinishScan(node->nl_pcache);
+
 		}
 
 		/*
@@ -157,7 +174,7 @@ ExecNestLoop(PlanState *pstate)
 		 */
 		ENL1_printf("getting new inner tuple");
 
-		innerTupleSlot = ExecProcNode(innerPlan);
+		innerTupleSlot = FetchInnerTuple(node->nl_pcache, innerPlan);
 		econtext->ecxt_innertuple = innerTupleSlot;
 
 		if (TupIsNull(innerTupleSlot))
@@ -345,6 +362,13 @@ ExecInitNestLoop(NestLoop *node, EState *estate, int eflags)
 	 */
 	nlstate->nl_NeedNewOuter = true;
 	nlstate->nl_MatchedOuter = false;
+	nlstate->nl_ParamCache = node->paramcache;
+
+	/* Setup the result cache if enabled */
+	if (nlstate->nl_ParamCache)
+		nlstate->nl_pcache = ExecInitResultCache(node, (PlanState *) nlstate, (PlanState *) innerPlanState(nlstate));
+	else
+		nlstate->nl_pcache = NULL;
 
 	NL1_printf("ExecInitNestLoop: %s\n",
 			   "node initialized");
@@ -352,6 +376,7 @@ ExecInitNestLoop(NestLoop *node, EState *estate, int eflags)
 	return nlstate;
 }
 
+
 /* ----------------------------------------------------------------
  *		ExecEndNestLoop
  *
@@ -380,6 +405,9 @@ ExecEndNestLoop(NestLoopState *node)
 	ExecEndNode(outerPlanState(node));
 	ExecEndNode(innerPlanState(node));
 
+	if (node->nl_pcache)
+		ExecEndResultCache(node->nl_pcache);
+
 	NL1_printf("ExecEndNestLoop: %s\n",
 			   "node processing ended");
 }
diff --git a/src/backend/executor/nodeResultCache.c b/src/backend/executor/nodeResultCache.c
index 09b25ea184..da5edf9c06 100644
--- a/src/backend/executor/nodeResultCache.c
+++ b/src/backend/executor/nodeResultCache.c
@@ -66,7 +66,6 @@
 										 * subplan without caching anything */
 #define RC_END_OF_SCAN				5	/* Ready for rescan */
 
-
 /* Helper macros for memory accounting */
 #define EMPTY_ENTRY_MEMORY_BYTES(e)		(sizeof(ResultCacheEntry) + \
 										 sizeof(ResultCacheKey) + \
@@ -179,7 +178,7 @@ ResultCacheHash_equal(struct resultcache_hash *tb, const ResultCacheKey *key1,
 					  const ResultCacheKey *key2)
 {
 	ResultCacheState *rcstate = (ResultCacheState *) tb->private_data;
-	ExprContext *econtext = rcstate->ss.ps.ps_ExprContext;
+	ExprContext *econtext = rcstate->ps_ExprContext;
 	TupleTableSlot *tslot = rcstate->tableslot;
 	TupleTableSlot *pslot = rcstate->probeslot;
 
@@ -223,7 +222,7 @@ prepare_probe_slot(ResultCacheState *rcstate, ResultCacheKey *key)
 		/* Set the probeslot's values based on the current parameter values */
 		for (int i = 0; i < numKeys; i++)
 			pslot->tts_values[i] = ExecEvalExpr(rcstate->param_exprs[i],
-												rcstate->ss.ps.ps_ExprContext,
+												rcstate->ps_ExprContext,
 												&pslot->tts_isnull[i]);
 	}
 	else
@@ -243,7 +242,7 @@ prepare_probe_slot(ResultCacheState *rcstate, ResultCacheKey *key)
  *		Remove all tuples from a cache entry, leaving an empty cache entry.
  *		Also update memory accounting to reflect the removal of the tuples.
  */
-static inline void
+static void
 entry_purge_tuples(ResultCacheState *rcstate, ResultCacheEntry *entry)
 {
 	ResultCacheTuple *tuple = entry->tuplehead;
@@ -590,21 +589,32 @@ cache_store_tuple(ResultCacheState *rcstate, TupleTableSlot *slot)
 	return true;
 }
 
-static TupleTableSlot *
-ExecResultCache(PlanState *pstate)
+/*
+ * Caller to call this after it finishes a parameterized scan
+ */
+void
+ExecResultCacheFinishScan(ResultCacheState *rcstate)
+{
+	rcstate->rc_status = RC_CACHE_LOOKUP;
+
+	/* nullify pointers used for the last scan */
+	rcstate->entry = NULL;
+	rcstate->last_tuple = NULL;
+}
+
+TupleTableSlot *
+ExecResultCache(ResultCacheState *rcstate, PlanState *innerPlan)
 {
-	ResultCacheState *node = castNode(ResultCacheState, pstate);
-	PlanState  *outerNode;
 	TupleTableSlot *slot;
 
-	switch (node->rc_status)
+	switch (rcstate->rc_status)
 	{
 		case RC_CACHE_LOOKUP:
 			{
 				ResultCacheEntry *entry;
 				bool		found;
 
-				Assert(node->entry == NULL);
+				Assert(rcstate->entry == NULL);
 
 				/*
 				 * We're only ever in this state for the first call of the
@@ -619,44 +629,43 @@ ExecResultCache(PlanState *pstate)
 				 * one there, we'll try to cache it.
 				 */
 
-				/* see if we've got anything cached for the current parameters */
-				entry = cache_lookup(node, &found);
+				 /* see if we've got anything cached for the current parameters */
+				entry = cache_lookup(rcstate, &found);
 
 				if (found && entry->complete)
 				{
-					node->stats.cache_hits += 1;	/* stats update */
+					rcstate->stats.cache_hits += 1;	/* stats update */
 
 					/*
 					 * Set last_tuple and entry so that the state
 					 * RC_CACHE_FETCH_NEXT_TUPLE can easily find the next
 					 * tuple for these parameters.
 					 */
-					node->last_tuple = entry->tuplehead;
-					node->entry = entry;
+					rcstate->last_tuple = entry->tuplehead;
+					rcstate->entry = entry;
 
 					/* Fetch the first cached tuple, if there is one */
 					if (entry->tuplehead)
 					{
-						node->rc_status = RC_CACHE_FETCH_NEXT_TUPLE;
+						rcstate->rc_status = RC_CACHE_FETCH_NEXT_TUPLE;
 
-						slot = node->ss.ps.ps_ResultTupleSlot;
-						ExecStoreMinimalTuple(entry->tuplehead->mintuple,
-											  slot, false);
-
-						return slot;
+						ExecClearTuple(rcstate->cachefoundslot);
+						slot = rcstate->cachefoundslotmin;
+						ExecStoreMinimalTuple(rcstate->last_tuple->mintuple, slot, false);
+						return ExecCopySlot(rcstate->cachefoundslot, slot);
 					}
 					else
 					{
 						/* The cache entry is void of any tuples. */
-						node->rc_status = RC_END_OF_SCAN;
+						rcstate->rc_status = RC_END_OF_SCAN;
 						return NULL;
 					}
 				}
 				else
 				{
-					TupleTableSlot *outerslot;
+					TupleTableSlot *innerslot;
 
-					node->stats.cache_misses += 1;	/* stats update */
+					rcstate->stats.cache_misses += 1;	/* stats update */
 
 					if (found)
 					{
@@ -668,13 +677,12 @@ ExecResultCache(PlanState *pstate)
 						 * guarantee the outer node will produce the tuples in
 						 * the same order as it did last time.
 						 */
-						entry_purge_tuples(node, entry);
+						entry_purge_tuples(rcstate, entry);
 					}
 
 					/* Scan the outer node for a tuple to cache */
-					outerNode = outerPlanState(node);
-					outerslot = ExecProcNode(outerNode);
-					if (TupIsNull(outerslot))
+					innerslot = ExecProcNode(innerPlan);
+					if (TupIsNull(innerslot))
 					{
 						/*
 						 * cache_lookup may have returned NULL due to failure
@@ -686,22 +694,22 @@ ExecResultCache(PlanState *pstate)
 						if (likely(entry))
 							entry->complete = true;
 
-						node->rc_status = RC_END_OF_SCAN;
+						rcstate->rc_status = RC_END_OF_SCAN;
 						return NULL;
 					}
 
-					node->entry = entry;
+					rcstate->entry = entry;
 
 					/*
 					 * If we failed to create the entry or failed to store the
 					 * tuple in the entry, then go into bypass mode.
 					 */
 					if (unlikely(entry == NULL ||
-								 !cache_store_tuple(node, outerslot)))
+						!cache_store_tuple(rcstate, innerslot)))
 					{
-						node->stats.cache_overflows += 1;	/* stats update */
+						rcstate->stats.cache_overflows += 1;	/* stats update */
 
-						node->rc_status = RC_CACHE_BYPASS_MODE;
+						rcstate->rc_status = RC_CACHE_BYPASS_MODE;
 
 						/*
 						 * No need to clear out last_tuple as we'll stay in
@@ -716,43 +724,41 @@ ExecResultCache(PlanState *pstate)
 						 * allows cache lookups to work even when the scan has
 						 * not been executed to completion.
 						 */
-						entry->complete = node->singlerow;
-						node->rc_status = RC_FILLING_CACHE;
+						entry->complete = rcstate->singlerow;
+						rcstate->rc_status = RC_FILLING_CACHE;
 					}
 
-					slot = node->ss.ps.ps_ResultTupleSlot;
-					ExecCopySlot(slot, outerslot);
-					return slot;
+					return innerslot;
 				}
 			}
 
 		case RC_CACHE_FETCH_NEXT_TUPLE:
 			{
 				/* We shouldn't be in this state if these are not set */
-				Assert(node->entry != NULL);
-				Assert(node->last_tuple != NULL);
+				Assert(rcstate->entry != NULL);
+				Assert(rcstate->last_tuple != NULL);
 
 				/* Skip to the next tuple to output */
-				node->last_tuple = node->last_tuple->next;
+				rcstate->last_tuple = rcstate->last_tuple->next;
 
 				/* No more tuples in the cache */
-				if (node->last_tuple == NULL)
+				if (rcstate->last_tuple == NULL)
 				{
-					node->rc_status = RC_END_OF_SCAN;
+					rcstate->rc_status = RC_END_OF_SCAN;
 					return NULL;
 				}
 
-				slot = node->ss.ps.ps_ResultTupleSlot;
-				ExecStoreMinimalTuple(node->last_tuple->mintuple, slot,
-									  false);
+				ExecClearTuple(rcstate->cachefoundslot);
+				slot = rcstate->cachefoundslotmin;
+				ExecStoreMinimalTuple(rcstate->last_tuple->mintuple, slot, false);
 
-				return slot;
+				return ExecCopySlot(rcstate->cachefoundslot, slot);
 			}
 
 		case RC_FILLING_CACHE:
 			{
-				TupleTableSlot *outerslot;
-				ResultCacheEntry *entry = node->entry;
+				TupleTableSlot *innerslot;
+				ResultCacheEntry *entry = rcstate->entry;
 
 				/* entry should already have been set by RC_CACHE_LOOKUP */
 				Assert(entry != NULL);
@@ -762,13 +768,12 @@ ExecResultCache(PlanState *pstate)
 				 * miss and are populating the cache with the current scan
 				 * tuples.
 				 */
-				outerNode = outerPlanState(node);
-				outerslot = ExecProcNode(outerNode);
-				if (TupIsNull(outerslot))
+				innerslot = ExecProcNode(innerPlan);
+				if (TupIsNull(innerslot))
 				{
 					/* No more tuples.  Mark it as complete */
 					entry->complete = true;
-					node->rc_status = RC_END_OF_SCAN;
+					rcstate->rc_status = RC_END_OF_SCAN;
 					return NULL;
 				}
 				else
@@ -782,12 +787,12 @@ ExecResultCache(PlanState *pstate)
 						elog(ERROR, "cache entry already complete");
 
 					/* Record the tuple in the current cache entry */
-					if (unlikely(!cache_store_tuple(node, outerslot)))
+					if (unlikely(!cache_store_tuple(rcstate, innerslot)))
 					{
 						/* Couldn't store it?  Handle overflow */
-						node->stats.cache_overflows += 1;	/* stats update */
+						rcstate->stats.cache_overflows += 1;	/* stats update */
 
-						node->rc_status = RC_CACHE_BYPASS_MODE;
+						rcstate->rc_status = RC_CACHE_BYPASS_MODE;
 
 						/*
 						 * No need to clear out entry or last_tuple as we'll
@@ -795,32 +800,27 @@ ExecResultCache(PlanState *pstate)
 						 */
 					}
 
-					slot = node->ss.ps.ps_ResultTupleSlot;
-					ExecCopySlot(slot, outerslot);
-					return slot;
+					return innerslot;
 				}
 			}
 
 		case RC_CACHE_BYPASS_MODE:
 			{
-				TupleTableSlot *outerslot;
+				TupleTableSlot *innerslot;
 
 				/*
 				 * When in bypass mode we just continue to read tuples without
 				 * caching.  We need to wait until the next rescan before we
 				 * can come out of this mode.
 				 */
-				outerNode = outerPlanState(node);
-				outerslot = ExecProcNode(outerNode);
-				if (TupIsNull(outerslot))
+				innerslot = ExecProcNode(innerPlan);
+				if (TupIsNull(innerslot))
 				{
-					node->rc_status = RC_END_OF_SCAN;
+					rcstate->rc_status = RC_END_OF_SCAN;
 					return NULL;
 				}
 
-				slot = node->ss.ps.ps_ResultTupleSlot;
-				ExecCopySlot(slot, outerslot);
-				return slot;
+				return innerslot;
 			}
 
 		case RC_END_OF_SCAN:
@@ -833,60 +833,34 @@ ExecResultCache(PlanState *pstate)
 
 		default:
 			elog(ERROR, "unrecognized resultcache state: %d",
-				 (int) node->rc_status);
+				 (int) rcstate->rc_status);
 			return NULL;
 	}							/* switch */
 }
 
 ResultCacheState *
-ExecInitResultCache(ResultCache *node, EState *estate, int eflags)
+ExecInitResultCache(NestLoop *node, PlanState *planstate, PlanState *cache_planstate)
 {
 	ResultCacheState *rcstate = makeNode(ResultCacheState);
-	Plan	   *outerNode;
 	int			i;
 	int			nkeys;
 	Oid		   *eqfuncoids;
 
-	/* check for unsupported flags */
-	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
-
-	rcstate->ss.ps.plan = (Plan *) node;
-	rcstate->ss.ps.state = estate;
-	rcstate->ss.ps.ExecProcNode = ExecResultCache;
-
-	/*
-	 * Miscellaneous initialization
-	 *
-	 * create expression context for node
-	 */
-	ExecAssignExprContext(estate, &rcstate->ss.ps);
-
-	outerNode = outerPlan(node);
-	outerPlanState(rcstate) = ExecInitNode(outerNode, estate, eflags);
-
-	/*
-	 * Initialize return slot and type. No need to initialize projection info
-	 * because this node doesn't do projections.
-	 */
-	ExecInitResultTupleSlotTL(&rcstate->ss.ps, &TTSOpsMinimalTuple);
-	rcstate->ss.ps.ps_ProjInfo = NULL;
-
-	/*
-	 * Initialize scan slot and type.
-	 */
-	ExecCreateScanSlotFromOuterPlan(estate, &rcstate->ss, &TTSOpsMinimalTuple);
-
-	/*
-	 * Set the state machine to lookup the cache.  We won't find anything
-	 * until we cache something, but this saves a special case to create the
-	 * first entry.
-	 */
+	rcstate->ps_ExprContext = CreateExprContext(planstate->state);
 	rcstate->rc_status = RC_CACHE_LOOKUP;
 
 	rcstate->nkeys = nkeys = node->numKeys;
 	rcstate->hashkeydesc = ExecTypeFromExprList(node->param_exprs);
 	rcstate->tableslot = MakeSingleTupleTableSlot(rcstate->hashkeydesc,
 												  &TTSOpsMinimalTuple);
+	/* XXX this should make a slot the same type as cache_planstates result slot.  For now
+	 * that'll always be a nested loop, so just make a virtual slot, which is what nested loop
+	 * uses.
+	 */
+	rcstate->cachefoundslot = MakeSingleTupleTableSlot(cache_planstate->ps_ResultTupleDesc,
+		&TTSOpsVirtual);
+	rcstate->cachefoundslotmin = MakeSingleTupleTableSlot(cache_planstate->ps_ResultTupleDesc,
+		&TTSOpsMinimalTuple);
 	rcstate->probeslot = MakeSingleTupleTableSlot(rcstate->hashkeydesc,
 												  &TTSOpsVirtual);
 
@@ -910,7 +884,7 @@ ExecInitResultCache(ResultCache *node, EState *estate, int eflags)
 
 		fmgr_info(left_hashfn, &rcstate->hashfunctions[i]);
 
-		rcstate->param_exprs[i] = ExecInitExpr(param_expr, (PlanState *) rcstate);
+		rcstate->param_exprs[i] = ExecInitExpr(param_expr, (PlanState *)planstate);
 		eqfuncoids[i] = get_opcode(hashop);
 	}
 
@@ -919,7 +893,7 @@ ExecInitResultCache(ResultCache *node, EState *estate, int eflags)
 													eqfuncoids,
 													node->collations,
 													node->param_exprs,
-													(PlanState *) rcstate);
+													(PlanState *) planstate);
 
 	pfree(eqfuncoids);
 	rcstate->mem_used = 0;
@@ -970,57 +944,12 @@ ExecInitResultCache(ResultCache *node, EState *estate, int eflags)
 void
 ExecEndResultCache(ResultCacheState *node)
 {
-	/*
-	 * When ending a parallel worker, copy the statistics gathered by the
-	 * worker back into shared memory so that it can be picked up by the main
-	 * process to report in EXPLAIN ANALYZE.
-	 */
-	if (node->shared_info && IsParallelWorker())
-	{
-		ResultCacheInstrumentation *si;
-
-		Assert(ParallelWorkerNumber <= node->shared_info->num_workers);
-		si = &node->shared_info->sinstrument[ParallelWorkerNumber];
-		memcpy(si, &node->stats, sizeof(ResultCacheInstrumentation));
-	}
-
 	/* Remove the cache context */
 	MemoryContextDelete(node->tableContext);
 
-	ExecClearTuple(node->ss.ss_ScanTupleSlot);
-	/* must drop pointer to cache result tuple */
-	ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
-
-	/*
-	 * free exprcontext
-	 */
-	ExecFreeExprContext(&node->ss.ps);
-
-	/*
-	 * shut down the subplan
-	 */
-	ExecEndNode(outerPlanState(node));
-}
-
-void
-ExecReScanResultCache(ResultCacheState *node)
-{
-	PlanState  *outerPlan = outerPlanState(node);
-
-	/* Mark that we must lookup the cache for a new set of parameters */
-	node->rc_status = RC_CACHE_LOOKUP;
-
-	/* nullify pointers used for the last scan */
-	node->entry = NULL;
-	node->last_tuple = NULL;
-
-	/*
-	 * if chgParam of subnode is not null then plan will be re-scanned by
-	 * first ExecProcNode.
-	 */
-	if (outerPlan->chgParam == NULL)
-		ExecReScan(outerPlan);
-
+	ExecClearTuple(node->cachefoundslot);
+	ExecClearTuple(node->cachefoundslotmin);
+	FreeExprContext(node->ps_ExprContext, false);
 }
 
 /*
@@ -1035,88 +964,3 @@ ExecEstimateCacheEntryOverheadBytes(double ntuples)
 		sizeof(ResultCacheTuple) * ntuples;
 }
 
-/* ----------------------------------------------------------------
- *						Parallel Query Support
- * ----------------------------------------------------------------
- */
-
- /* ----------------------------------------------------------------
-  *		ExecResultCacheEstimate
-  *
-  *		Estimate space required to propagate result cache statistics.
-  * ----------------------------------------------------------------
-  */
-void
-ExecResultCacheEstimate(ResultCacheState *node, ParallelContext *pcxt)
-{
-	Size		size;
-
-	/* don't need this if not instrumenting or no workers */
-	if (!node->ss.ps.instrument || pcxt->nworkers == 0)
-		return;
-
-	size = mul_size(pcxt->nworkers, sizeof(ResultCacheInstrumentation));
-	size = add_size(size, offsetof(SharedResultCacheInfo, sinstrument));
-	shm_toc_estimate_chunk(&pcxt->estimator, size);
-	shm_toc_estimate_keys(&pcxt->estimator, 1);
-}
-
-/* ----------------------------------------------------------------
- *		ExecResultCacheInitializeDSM
- *
- *		Initialize DSM space for result cache statistics.
- * ----------------------------------------------------------------
- */
-void
-ExecResultCacheInitializeDSM(ResultCacheState *node, ParallelContext *pcxt)
-{
-	Size		size;
-
-	/* don't need this if not instrumenting or no workers */
-	if (!node->ss.ps.instrument || pcxt->nworkers == 0)
-		return;
-
-	size = offsetof(SharedResultCacheInfo, sinstrument)
-		+ pcxt->nworkers * sizeof(ResultCacheInstrumentation);
-	node->shared_info = shm_toc_allocate(pcxt->toc, size);
-	/* ensure any unfilled slots will contain zeroes */
-	memset(node->shared_info, 0, size);
-	node->shared_info->num_workers = pcxt->nworkers;
-	shm_toc_insert(pcxt->toc, node->ss.ps.plan->plan_node_id,
-				   node->shared_info);
-}
-
-/* ----------------------------------------------------------------
- *		ExecResultCacheInitializeWorker
- *
- *		Attach worker to DSM space for result cache statistics.
- * ----------------------------------------------------------------
- */
-void
-ExecResultCacheInitializeWorker(ResultCacheState *node, ParallelWorkerContext *pwcxt)
-{
-	node->shared_info =
-		shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, true);
-}
-
-/* ----------------------------------------------------------------
- *		ExecResultCacheRetrieveInstrumentation
- *
- *		Transfer result cache statistics from DSM to private memory.
- * ----------------------------------------------------------------
- */
-void
-ExecResultCacheRetrieveInstrumentation(ResultCacheState *node)
-{
-	Size		size;
-	SharedResultCacheInfo *si;
-
-	if (node->shared_info == NULL)
-		return;
-
-	size = offsetof(SharedResultCacheInfo, sinstrument)
-		+ node->shared_info->num_workers * sizeof(ResultCacheInstrumentation);
-	si = palloc(size);
-	memcpy(si, node->shared_info, size);
-	node->shared_info = si;
-}
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index e50844df9b..0101d719c4 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -2298,148 +2298,6 @@ cost_material(Path *path,
 	path->total_cost = startup_cost + run_cost;
 }
 
-/*
- * cost_resultcache_rescan
- *	  Determines the estimated cost of rescanning a ResultCache node.
- *
- * In order to estimate this, we must gain knowledge of how often we expect to
- * be called and how many distinct sets of parameters we are likely to be
- * called with. If we expect a good cache hit ratio, then we can set our
- * costs to account for that hit ratio, plus a little bit of cost for the
- * caching itself.  Caching will not work out well if we expect to be called
- * with too many distinct parameter values.  The worst-case here is that we
- * never see the same parameter values twice, in which case we'd never get a
- * cache hit and caching would be a complete waste of effort.
- */
-static void
-cost_resultcache_rescan(PlannerInfo *root, ResultCachePath *rcpath,
-						Cost *rescan_startup_cost, Cost *rescan_total_cost)
-{
-	Cost		input_startup_cost = rcpath->subpath->startup_cost;
-	Cost		input_total_cost = rcpath->subpath->total_cost;
-	double		tuples = rcpath->subpath->rows;
-	double		calls = rcpath->calls;
-	int			width = rcpath->subpath->pathtarget->width;
-	int			flags;
-
-	double		work_mem_bytes;
-	double		est_entry_bytes;
-	double		est_cache_entries;
-	double		ndistinct;
-	double		evict_ratio;
-	double		hit_ratio;
-	Cost		startup_cost;
-	Cost		total_cost;
-
-	/* available cache space */
-	work_mem_bytes = work_mem * 1024L;
-
-	/*
-	 * Set the number of bytes each cache entry should consume in the cache.
-	 * To provide us with better estimations on how many cache entries we can
-	 * store at once we make a call to the excutor here to ask it what memory
-	 * overheads there are for a single cache entry.
-	 *
-	 * XXX we also store the cache key, but that's not accounted for here.
-	 */
-	est_entry_bytes = relation_byte_size(tuples, width) +
-		ExecEstimateCacheEntryOverheadBytes(tuples);
-
-	/* estimate on the upper limit of cache entries we can hold at once */
-	est_cache_entries = floor(work_mem_bytes / est_entry_bytes);
-
-	/* estimate on the distinct number of parameter values */
-	ndistinct = estimate_num_groups(root, rcpath->param_exprs, calls, NULL,
-									&flags);
-
-	/*
-	 * When the estimation fell back on using a default value, it's a bit too
-	 * risky to assume that it's ok to use a Result Cache.  The use of a
-	 * default could cause us to use a Result Cache when it's really
-	 * inappropriate to do so.  If we see that this has been done then we'll
-	 * assume that every call will have unique parameters, which will almost
-	 * certainly mean a ResultCachePath will never survive add_path().
-	 */
-	if ((flags & SELFLAG_USED_DEFAULT) != 0)
-		ndistinct = calls;
-
-	/*
-	 * Since we've already estimated the maximum number of entries we can
-	 * store at once and know the estimated number of distinct values we'll be
-	 * called with, well take this opportunity to set the path's est_entries.
-	 * This will ultimately determine the hash table size that the executor
-	 * will use.  If we leave this at zero the executor will just choose the
-	 * size itself.  Really this is not the right place to do this, but it's
-	 * convenient since everything is already calculated.
-	 */
-	rcpath->est_entries = Min(Min(ndistinct, est_cache_entries),
-							  PG_UINT32_MAX);
-
-
-	/*
-	 * When the number of distinct parameter values is above the amount we can
-	 * store in the cache, then we'll have to evict some entries from the
-	 * cache.  This is not free, so here we estimate how often we'll incur the
-	 * cost of that eviction.
-	 */
-	evict_ratio = 1.0 - Min(est_cache_entries, ndistinct) / ndistinct;
-
-	/*
-	 * In order to estimate how costly a single scan will be, we need to
-	 * attempt to estimate what the cache hit ratio will be.  To do that we
-	 * must look at how many scans are estimated in total of this node and how
-	 * many of those scans we expect to get a cache hit.
-	 */
-	hit_ratio = 1.0 / ndistinct * Min(est_cache_entries, ndistinct) -
-		(ndistinct / calls);
-
-	/* Ensure we don't go negative */
-	hit_ratio = Max(hit_ratio, 0);
-
-	/*
-	 * Set the total_cost accounting for the expected cache hit ratio.  We
-	 * also add on a cpu_operator_cost to account for a cache lookup. This
-	 * will happen regardless of if it's a cache hit or not.
-	 */
-	total_cost = input_total_cost * (1.0 - hit_ratio) + cpu_operator_cost;
-
-	/* Now adjust the total cost to account for cache evictions */
-
-	/* Charge a cpu_tuple_cost for evicting the actual cache entry */
-	total_cost += cpu_tuple_cost * evict_ratio;
-
-	/*
-	 * Charge a 10th of cpu_operator_cost to evict every tuple in that entry.
-	 * The per-tuple eviction is really just a pfree, so charging a whole
-	 * cpu_operator_cost seems a little excessive.
-	 */
-	total_cost += cpu_operator_cost / 10.0 * evict_ratio * tuples;
-
-	/*
-	 * Now adjust for storing things in the cache, since that's not free
-	 * either.  Everything must go in the cache, so we don't proportion this
-	 * over any ratio, just apply it once for the scan.  We charge a
-	 * cpu_tuple_cost for the creation of the cache entry and also a
-	 * cpu_operator_cost for each tuple we expect to cache.
-	 */
-	total_cost += cpu_tuple_cost + cpu_operator_cost * tuples;
-
-	/*
-	 * Getting the first row must be also be proportioned according to the
-	 * expected cache hit ratio.
-	 */
-	startup_cost = input_startup_cost * (1.0 - hit_ratio);
-
-	/*
-	 * Additionally we charge a cpu_tuple_cost to account for cache lookups,
-	 * which we'll do regardless of if it was a cache hit or not.
-	 */
-	startup_cost += cpu_tuple_cost;
-
-	*rescan_startup_cost = startup_cost;
-	*rescan_total_cost = total_cost;
-}
-
 /*
  * cost_agg
  *		Determines and returns the cost of performing an Agg plan node,
@@ -4167,11 +4025,6 @@ cost_rescan(PlannerInfo *root, Path *path,
 				*rescan_total_cost = run_cost;
 			}
 			break;
-		case T_ResultCache:
-			/* All the hard work is done by cost_resultcache_rescan */
-			cost_resultcache_rescan(root, (ResultCachePath *) path,
-									rescan_startup_cost, rescan_total_cost);
-			break;
 		default:
 			*rescan_startup_cost = path->startup_cost;
 			*rescan_total_cost = path->total_cost;
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index f4c76577ad..5918dd9a3a 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -17,13 +17,16 @@
 #include <math.h>
 
 #include "executor/executor.h"
+#include "executor/nodeResultCache.h"
 #include "foreign/fdwapi.h"
+#include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/cost.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
+#include "utils/selfuncs.h"
 #include "utils/typcache.h"
 
 /* Hook for plugins to get control in add_paths_to_joinrel() */
@@ -554,6 +557,152 @@ get_resultcache_path(PlannerInfo *root, RelOptInfo *innerrel,
 	return NULL;
 }
 
+static double
+relation_byte_size(double tuples, int width)
+{
+	return tuples * (MAXALIGN(width) + MAXALIGN(SizeofHeapTupleHeader));
+}
+
+/*
+ * cost_resultcache_rescan
+ *	  Determines the estimated cost of rescanning a ResultCache node.
+ *
+ * In order to estimate this, we must gain knowledge of how often we expect to
+ * be called and how many distinct sets of parameters we are likely to be
+ * called with. If we expect a good cache hit ratio, then we can set our
+ * costs to account for that hit ratio, plus a little bit of cost for the
+ * caching itself.  Caching will not work out well if we expect to be called
+ * with too many distinct parameter values.  The worst-case here is that we
+ * never see the same parameter values twice, in which case we'd never get a
+ * cache hit and caching would be a complete waste of effort.
+ */
+static bool
+use_nestedloop_cache(PlannerInfo *root, NestPath *nlpath)
+{
+	Cost		input_startup_cost = nlpath->innerjoinpath->startup_cost;
+	Cost		input_total_cost = nlpath->innerjoinpath->total_cost;
+	double		tuples = nlpath->innerjoinpath->rows;
+	double		calls = nlpath->outerjoinpath->rows;
+	int			width = nlpath->innerjoinpath->pathtarget->width;
+	int			flags;
+
+	double		work_mem_bytes;
+	double		est_entry_bytes;
+	double		est_cache_entries;
+	double		ndistinct;
+	double		evict_ratio;
+	double		hit_ratio;
+	Cost		startup_cost;
+	Cost		total_cost;
+
+	/* available cache space */
+	work_mem_bytes = work_mem * 1024L;
+
+	/*
+	 * Set the number of bytes each cache entry should consume in the cache.
+	 * To provide us with better estimations on how many cache entries we can
+	 * store at once we make a call to the excutor here to ask it what memory
+	 * overheads there are for a single cache entry.
+	 *
+	 * XXX we also store the cache key, but that's not accounted for here.
+	 */
+	est_entry_bytes = relation_byte_size(tuples, width) +
+		ExecEstimateCacheEntryOverheadBytes(tuples);
+
+	/* estimate on the upper limit of cache entries we can hold at once */
+	est_cache_entries = floor(work_mem_bytes / est_entry_bytes);
+
+	/* estimate on the distinct number of parameter values */
+	ndistinct = 1; // estimate_num_groups(root, nlpath->rcpath->param_exprs, calls, NULL,
+		//&flags);
+
+	/*
+	 * When the estimation fell back on using a default value, it's a bit too
+	 * risky to assume that it's ok to use a Result Cache.  The use of a
+	 * default could cause us to use a Result Cache when it's really
+	 * inappropriate to do so.  If we see that this has been done then we'll
+	 * assume that every call will have unique parameters, which will almost
+	 * certainly mean a ResultCachePath will never survive add_path().
+	 */
+	if ((flags & SELFLAG_USED_DEFAULT) != 0)
+		ndistinct = calls;
+
+	/*
+	 * Since we've already estimated the maximum number of entries we can
+	 * store at once and know the estimated number of distinct values we'll be
+	 * called with, well take this opportunity to set the path's est_entries.
+	 * This will ultimately determine the hash table size that the executor
+	 * will use.  If we leave this at zero the executor will just choose the
+	 * size itself.  Really this is not the right place to do this, but it's
+	 * convenient since everything is already calculated.
+	 */
+	//nlpath->est_entries = Min(Min(ndistinct, est_cache_entries),
+	//	PG_UINT32_MAX);
+
+
+	/*
+	 * When the number of distinct parameter values is above the amount we can
+	 * store in the cache, then we'll have to evict some entries from the
+	 * cache.  This is not free, so here we estimate how often we'll incur the
+	 * cost of that eviction.
+	 */
+	evict_ratio = 1.0 - Min(est_cache_entries, ndistinct) / ndistinct;
+
+	/*
+	 * In order to estimate how costly a single scan will be, we need to
+	 * attempt to estimate what the cache hit ratio will be.  To do that we
+	 * must look at how many scans are estimated in total of this node and how
+	 * many of those scans we expect to get a cache hit.
+	 */
+	hit_ratio = 1.0 / ndistinct * Min(est_cache_entries, ndistinct) -
+		(ndistinct / calls);
+
+	/* Ensure we don't go negative */
+	hit_ratio = Max(hit_ratio, 0);
+
+	/*
+	 * Set the total_cost accounting for the expected cache hit ratio.  We
+	 * also add on a cpu_operator_cost to account for a cache lookup. This
+	 * will happen regardless of if it's a cache hit or not.
+	 */
+	total_cost = input_total_cost * (1.0 - hit_ratio) + cpu_operator_cost;
+
+	/* Now adjust the total cost to account for cache evictions */
+
+	/* Charge a cpu_tuple_cost for evicting the actual cache entry */
+	total_cost += cpu_tuple_cost * evict_ratio;
+
+	/*
+	 * Charge a 10th of cpu_operator_cost to evict every tuple in that entry.
+	 * The per-tuple eviction is really just a pfree, so charging a whole
+	 * cpu_operator_cost seems a little excessive.
+	 */
+	total_cost += cpu_operator_cost / 10.0 * evict_ratio * tuples;
+
+	/*
+	 * Now adjust for storing things in the cache, since that's not free
+	 * either.  Everything must go in the cache, so we don't proportion this
+	 * over any ratio, just apply it once for the scan.  We charge a
+	 * cpu_tuple_cost for the creation of the cache entry and also a
+	 * cpu_operator_cost for each tuple we expect to cache.
+	 */
+	total_cost += cpu_tuple_cost + cpu_operator_cost * tuples;
+
+	/*
+	 * Getting the first row must be also be proportioned according to the
+	 * expected cache hit ratio.
+	 */
+	startup_cost = input_startup_cost * (1.0 - hit_ratio);
+
+	/*
+	 * Additionally we charge a cpu_tuple_cost to account for cache lookups,
+	 * which we'll do regardless of if it was a cache hit or not.
+	 */
+	startup_cost += cpu_tuple_cost;
+
+	return total_cost < nlpath->innerjoinpath->total_cost;
+}
+
 /*
  * try_nestloop_path
  *	  Consider a nestloop join path; if it appears useful, push it into
@@ -576,8 +725,7 @@ try_nestloop_path(PlannerInfo *root,
 	Relids		outerrelids;
 	Relids		inner_paramrels = PATH_REQ_OUTER(inner_path);
 	Relids		outer_paramrels = PATH_REQ_OUTER(outer_path);
-	Path	   *inner_cache_path;
-	bool		added_path = false;
+	ResultCachePath	   *rcpath;
 
 	/*
 	 * Paths are parameterized by top-level parents, so run parameterization
@@ -628,6 +776,7 @@ try_nestloop_path(PlannerInfo *root,
 						  workspace.startup_cost, workspace.total_cost,
 						  pathkeys, required_outer))
 	{
+		NestPath *nlpath;
 		/*
 		 * If the inner path is parameterized, it is parameterized by the
 		 * topmost parent of the outer rel, not the outer rel itself.  Fix
@@ -649,103 +798,37 @@ try_nestloop_path(PlannerInfo *root,
 			}
 		}
 
-		add_path(joinrel, (Path *)
-				 create_nestloop_path(root,
-									  joinrel,
-									  jointype,
-									  &workspace,
-									  extra,
-									  outer_path,
-									  inner_path,
-									  extra->restrictlist,
-									  pathkeys,
-									  required_outer));
-		added_path = true;
-	}
-
-	/*
-	 * See if we can build a result cache path for this inner_path. That might
-	 * make the nested loop cheaper.
-	 */
-	inner_cache_path = get_resultcache_path(root, innerrel, outerrel,
-											inner_path, outer_path, jointype,
-											extra);
-
-	if (inner_cache_path == NULL)
-	{
-		if (!added_path)
-			bms_free(required_outer);
-		return;
-	}
-
-	initial_cost_nestloop(root, &workspace, jointype,
-						  outer_path, inner_cache_path, extra);
-
-	if (add_path_precheck(joinrel,
-						  workspace.startup_cost, workspace.total_cost,
-						  pathkeys, required_outer))
-	{
 		/*
-		 * If the inner path is parameterized, it is parameterized by the
-		 * topmost parent of the outer rel, not the outer rel itself.  Fix
-		 * that.
+		 * See if we can build a result cache path for this inner_path. That might
+		 * make the nested loop cheaper.
 		 */
-		if (PATH_PARAM_BY_PARENT(inner_cache_path, outer_path->parent))
+		rcpath = (ResultCachePath *) get_resultcache_path(root, innerrel, outerrel,
+			inner_path, outer_path, jointype,
+			extra);
+
+		nlpath = create_nestloop_path(root,
+			joinrel,
+			jointype,
+			&workspace,
+			extra,
+			outer_path,
+			inner_path,
+			extra->restrictlist,
+			pathkeys,
+			required_outer);
+
+		if (rcpath != NULL)
 		{
-			Path	   *reparameterize_path;
-
-			reparameterize_path = reparameterize_path_by_child(root,
-															   inner_cache_path,
-															   outer_path->parent);
-
-			/*
-			 * If we could not translate the path, we can't create nest loop
-			 * path.
-			 */
-			if (!reparameterize_path)
-			{
-				ResultCachePath *rcpath = (ResultCachePath *) inner_cache_path;
-
-				/* Waste no memory when we reject a path here */
-				list_free(rcpath->hash_operators);
-				list_free(rcpath->param_exprs);
-				pfree(rcpath);
-
-				if (!added_path)
-					bms_free(required_outer);
-				return;
-			}
+			nlpath->use_cache = true;
+			nlpath->hash_operators = rcpath->hash_operators;
+			nlpath->param_exprs = rcpath->param_exprs;
+			nlpath->singlerow = rcpath->singlerow;
+			nlpath->calls = rcpath->calls;
+			nlpath->est_entries = rcpath->est_entries;
 		}
 
-		add_path(joinrel, (Path *)
-				 create_nestloop_path(root,
-									  joinrel,
-									  jointype,
-									  &workspace,
-									  extra,
-									  outer_path,
-									  inner_cache_path,
-									  extra->restrictlist,
-									  pathkeys,
-									  required_outer));
-		added_path = true;
+		add_path(joinrel, (Path *)nlpath);
 	}
-	else
-	{
-		ResultCachePath *rcpath = (ResultCachePath *) inner_cache_path;
-
-		/* Waste no memory when we reject a path here */
-		list_free(rcpath->hash_operators);
-		list_free(rcpath->param_exprs);
-		pfree(rcpath);
-	}
-
-	if (!added_path)
-	{
-		/* Waste no memory when we reject a path here */
-		bms_free(required_outer);
-	}
-
 }
 
 /*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 45e211262a..7afb7741d0 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4147,6 +4147,7 @@ create_nestloop_plan(PlannerInfo *root,
 	Relids		outerrelids;
 	List	   *nestParams;
 	Relids		saveOuterRels = root->curOuterRels;
+	List	   *param_exprs = NIL;
 
 	/* NestLoop can project, so no need to be picky about child tlists */
 	outer_plan = create_plan_recurse(root, best_path->outerjoinpath, 0);
@@ -4157,6 +4158,9 @@ create_nestloop_plan(PlannerInfo *root,
 
 	inner_plan = create_plan_recurse(root, best_path->innerjoinpath, 0);
 
+	param_exprs = (List *) replace_nestloop_params(root, (Node *)
+												   best_path->param_exprs);
+
 	/* Restore curOuterRels */
 	bms_free(root->curOuterRels);
 	root->curOuterRels = saveOuterRels;
@@ -4204,6 +4208,54 @@ create_nestloop_plan(PlannerInfo *root,
 							  best_path->jointype,
 							  best_path->inner_unique);
 
+	//bool		paramcache;
+	//int			numKeys;		/* size of the two arrays below */
+
+	//Oid		   *hashOperators;	/* hash operators for each key */
+	//Oid		   *collations;		/* cache keys */
+	//List	   *param_exprs;	/* exprs containing parameters */
+	//bool		singlerow;		/* true if the cache entry should be marked as
+	//							 * complete after we store the first tuple in
+	//							 * it. */
+	//uint32		est_entries;	/* The maximum number of entries that the
+	//							 * planner expects will fit in the cache, or 0
+	//							 * if unknown */
+
+	if (best_path->use_cache)
+	{
+		Oid		   *operators;
+		Oid		   *collations;
+		ListCell   *lc;
+		ListCell   *lc2;
+		int			nkeys;
+		int			i;
+
+		join_plan->numKeys = list_length(best_path->param_exprs);
+
+		nkeys = list_length(param_exprs);
+		Assert(nkeys > 0);
+		operators = palloc(nkeys * sizeof(Oid));
+		collations = palloc(nkeys * sizeof(Oid));
+
+		i = 0;
+		forboth(lc, param_exprs, lc2, best_path->hash_operators)
+		{
+			Expr	   *param_expr = (Expr *)lfirst(lc);
+			Oid			opno = lfirst_oid(lc2);
+
+			operators[i] = opno;
+			collations[i] = exprCollation((Node *)param_expr);
+			i++;
+		}
+		join_plan->paramcache = true;
+		join_plan->param_exprs = param_exprs;
+		join_plan->hashOperators = operators;
+		join_plan->collations = collations;
+		join_plan->singlerow = best_path->singlerow;
+		join_plan->est_entries = best_path->est_entries;
+
+	}
+
 	copy_generic_path_info(&join_plan->join.plan, &best_path->path);
 
 	return join_plan;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 3e2c61b0a0..9da223139a 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -137,74 +137,6 @@ get_first_col_type(Plan *plan, Oid *coltype, int32 *coltypmod,
 	*colcollation = InvalidOid;
 }
 
-
-/*
- * outer_params_hashable
- *		Determine if it's valid to use a ResultCache node to cache already
- *		seen rows matching a given set of parameters instead of performing a
- *		rescan of the subplan pointed to by 'subroot'.  If it's valid, check
- *		if all parameters required by this query level can be hashed.  If so,
- *		return true and set 'operators' to the list of hash equality operators
- *		for the given parameters then populate 'param_exprs' with each
- *		PARAM_EXEC parameter that the subplan requires the outer query to pass
- *		it.  When hashing is not possible, false is returned and the two
- *		output lists are unchanged.
- */
-static bool
-outer_params_hashable(PlannerInfo *subroot, List *plan_params, List **operators,
-					  List **param_exprs)
-{
-	List	   *oplist = NIL;
-	List	   *exprlist = NIL;
-	ListCell   *lc;
-
-	/* Ensure we're not given a top-level query. */
-	Assert(subroot->parent_root != NULL);
-
-	/*
-	 * It's not valid to use a Result Cache node if there are any volatile
-	 * function in the subquery.  Caching could cause fewer evaluations of
-	 * volatile functions that have side-effects
-	 */
-	if (contain_volatile_functions((Node *) subroot->parse))
-		return false;
-
-	foreach(lc, plan_params)
-	{
-		PlannerParamItem *ppi = (PlannerParamItem *) lfirst(lc);
-		TypeCacheEntry *typentry;
-		Node	   *expr = ppi->item;
-		Param	   *param;
-
-		param = makeNode(Param);
-		param->paramkind = PARAM_EXEC;
-		param->paramid = ppi->paramId;
-		param->paramtype = exprType(expr);
-		param->paramtypmod = exprTypmod(expr);
-		param->paramcollid = exprCollation(expr);
-		param->location = -1;
-
-		typentry = lookup_type_cache(param->paramtype,
-									 TYPECACHE_HASH_PROC | TYPECACHE_EQ_OPR);
-
-		/* XXX will eq_opr ever be invalid if hash_proc isn't? */
-		if (!OidIsValid(typentry->hash_proc) || !OidIsValid(typentry->eq_opr))
-		{
-			list_free(oplist);
-			list_free(exprlist);
-			return false;
-		}
-
-		oplist = lappend_oid(oplist, typentry->eq_opr);
-		exprlist = lappend(exprlist, param);
-	}
-
-	*operators = oplist;
-	*param_exprs = exprlist;
-
-	return true;				/* all params can be hashed */
-}
-
 /*
  * Convert a SubLink (as created by the parser) into a SubPlan.
  *
@@ -311,30 +243,30 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
 	 * regardless. It may be useful if we can only do this when it seems
 	 * likely that we'll get some repeat lookups, i.e. cache hits.
 	 */
-	if (enable_resultcache && plan_params != NIL && subLinkType == EXPR_SUBLINK)
-	{
-		List	   *operators;
-		List	   *param_exprs;
-
-		/* Determine if all the subplan parameters can be hashed */
-		if (outer_params_hashable(subroot, plan_params, &operators, &param_exprs))
-		{
-			ResultCachePath *cache_path;
-
-			/*
-			 * Pass -1 for the number of calls since we don't have any ideas
-			 * what that'll be.
-			 */
-			cache_path = create_resultcache_path(root,
-												 best_path->parent,
-												 best_path,
-												 param_exprs,
-												 operators,
-												 false,
-												 -1);
-			best_path = (Path *) cache_path;
-		}
-	}
+	//if (enable_resultcache && plan_params != NIL && subLinkType == EXPR_SUBLINK)
+	//{
+	//	List	   *operators;
+	//	List	   *param_exprs;
+
+	//	/* Determine if all the subplan parameters can be hashed */
+	//	if (outer_params_hashable(subroot, plan_params, &operators, &param_exprs))
+	//	{
+	//		ResultCachePath *cache_path;
+
+	//		/*
+	//		 * Pass -1 for the number of calls since we don't have any ideas
+	//		 * what that'll be.
+	//		 */
+	//		cache_path = create_resultcache_path(root,
+	//											 best_path->parent,
+	//											 best_path,
+	//											 param_exprs,
+	//											 operators,
+	//											 false,
+	//											 -1);
+	//		best_path = (Path *) cache_path;
+	//	}
+	//}
 
 	plan = create_plan(subroot, best_path);
 
diff --git a/src/include/executor/nodeResultCache.h b/src/include/executor/nodeResultCache.h
index d2f3ed9a74..440019d141 100644
--- a/src/include/executor/nodeResultCache.h
+++ b/src/include/executor/nodeResultCache.h
@@ -15,16 +15,11 @@
 
 #include "nodes/execnodes.h"
 
-extern ResultCacheState *ExecInitResultCache(ResultCache *node, EState *estate, int eflags);
+extern void ExecResultCacheFinishScan(ResultCacheState *rcstate);
+extern TupleTableSlot *ExecResultCache(ResultCacheState *rcstate, PlanState *innerPlan);
+extern ResultCacheState *ExecInitResultCache(NestLoop *node, PlanState *planstate, PlanState *cache_planstate);
 extern void ExecEndResultCache(ResultCacheState *node);
 extern void ExecReScanResultCache(ResultCacheState *node);
 extern double ExecEstimateCacheEntryOverheadBytes(double ntuples);
-extern void ExecResultCacheEstimate(ResultCacheState *node,
-									ParallelContext *pcxt);
-extern void ExecResultCacheInitializeDSM(ResultCacheState *node,
-										 ParallelContext *pcxt);
-extern void ExecResultCacheInitializeWorker(ResultCacheState *node,
-											ParallelWorkerContext *pwcxt);
-extern void ExecResultCacheRetrieveInstrumentation(ResultCacheState *node);
 
 #endif							/* NODERESULTCACHE_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 30f66d5058..a2a70151c9 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1855,12 +1855,15 @@ typedef struct JoinState
  *		NullInnerTupleSlot prepared null tuple for left outer joins
  * ----------------
  */
+struct ResultCacheState;
 typedef struct NestLoopState
 {
 	JoinState	js;				/* its first field is NodeTag */
 	bool		nl_NeedNewOuter;
 	bool		nl_MatchedOuter;
+	bool		nl_ParamCache;
 	TupleTableSlot *nl_NullInnerTupleSlot;
+	struct ResultCacheState *nl_pcache;
 } NestLoopState;
 
 /* ----------------
@@ -2022,12 +2025,15 @@ typedef struct SharedResultCacheInfo
  */
 typedef struct ResultCacheState
 {
-	ScanState	ss;				/* its first field is NodeTag */
+	ExprContext *ps_ExprContext;	/* node's expression-evaluation context */
+	//ScanState	ss;				/* its first field is NodeTag */
 	int			rc_status;		/* value of ExecResultCache's state machine */
 	int			nkeys;			/* number of hash table keys */
 	struct resultcache_hash *hashtable; /* hash table cache entries */
 	TupleDesc	hashkeydesc;	/* tuple descriptor for hash keys */
 	TupleTableSlot *tableslot;	/* min tuple slot for existing cache entries */
+	TupleTableSlot *cachefoundslot; /* Slot to return found cache entries */
+	TupleTableSlot *cachefoundslotmin; /* Slot to return found cache entries */
 	TupleTableSlot *probeslot;	/* virtual slot used for hash lookups */
 	ExprState  *cache_eq_expr;	/* Compare exec params to hash key */
 	ExprState **param_exprs;	/* exprs containing the parameters to this
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 79a4ad20dd..31b158026c 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1546,6 +1546,16 @@ typedef struct JoinPath
 
 	List	   *joinrestrictinfo;	/* RestrictInfos to apply to join */
 
+	bool		use_cache;
+	List	   *hash_operators; /* hash operators for each key */
+	List	   *param_exprs;	/* cache keys */
+	bool		singlerow;		/* true if the cache entry is to be marked as
+								 * complete after caching the first record. */
+	double		calls;			/* expected number of rescans */
+	uint32		est_entries;	/* The maximum number of entries that the
+								 * planner expects will fit in the cache, or 0
+								 * if unknown */
+
 	/*
 	 * See the notes for RelOptInfo and ParamPathInfo to understand why
 	 * joinrestrictinfo is needed in JoinPath, and can't be merged into the
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index ac5685da64..f989d31033 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -701,6 +701,18 @@ typedef struct NestLoop
 {
 	Join		join;
 	List	   *nestParams;		/* list of NestLoopParam nodes */
+	bool		paramcache;
+	int			numKeys;		/* size of the two arrays below */
+
+	Oid		   *hashOperators;	/* hash operators for each key */
+	Oid		   *collations;		/* cache keys */
+	List	   *param_exprs;	/* exprs containing parameters */
+	bool		singlerow;		/* true if the cache entry should be marked as
+								 * complete after we store the first tuple in
+								 * it. */
+	uint32		est_entries;	/* The maximum number of entries that the
+								 * planner expects will fit in the cache, or 0
+								 * if unknown */
 } NestLoop;
 
 typedef struct NestLoopParam