diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ef5f2de994bda2682239a366b1ce67d4c399c3fe..c225e61be9685ad1b48616f3041b933dccedd2d2 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -19,7 +19,6 @@
 #include <limits.h>
 #include <math.h>
 
-#include "access/stratnum.h"
 #include "access/sysattr.h"
 #include "catalog/pg_class.h"
 #include "foreign/fdwapi.h"
@@ -29,11 +28,11 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/paramassign.h"
 #include "optimizer/paths.h"
 #include "optimizer/placeholder.h"
 #include "optimizer/plancat.h"
 #include "optimizer/planmain.h"
-#include "optimizer/planner.h"
 #include "optimizer/predtest.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/subselect.h"
@@ -152,8 +151,6 @@ static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path)
 static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path);
 static Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
 static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root);
-static void process_subquery_nestloop_params(PlannerInfo *root,
-								 List *subplan_params);
 static List *fix_indexqual_references(PlannerInfo *root, IndexPath *index_path);
 static List *fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path);
 static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol);
@@ -312,7 +309,7 @@ create_plan(PlannerInfo *root, Path *best_path)
 	/* plan_params should not be in use in current query level */
 	Assert(root->plan_params == NIL);
 
-	/* Initialize this module's private workspace in PlannerInfo */
+	/* Initialize this module's workspace in PlannerInfo */
 	root->curOuterRels = NULL;
 	root->curOuterParams = NIL;
 
@@ -1531,7 +1528,7 @@ create_gather_plan(PlannerInfo *root, GatherPath *best_path)
 	gather_plan = make_gather(tlist,
 							  NIL,
 							  best_path->num_workers,
-							  SS_assign_special_param(root),
+							  assign_special_exec_param(root),
 							  best_path->single_copy,
 							  subplan);
 
@@ -1567,7 +1564,7 @@ create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path)
 	copy_generic_path_info(&gm_plan->plan, &best_path->path);
 
 	/* Assign the rescan Param. */
-	gm_plan->rescan_param = SS_assign_special_param(root);
+	gm_plan->rescan_param = assign_special_exec_param(root);
 
 	/* Gather Merge is pointless with no pathkeys; use Gather instead. */
 	Assert(pathkeys != NIL);
@@ -3706,9 +3703,6 @@ create_nestloop_plan(PlannerInfo *root,
 	Relids		outerrelids;
 	List	   *nestParams;
 	Relids		saveOuterRels = root->curOuterRels;
-	ListCell   *cell;
-	ListCell   *prev;
-	ListCell   *next;
 
 	/* NestLoop can project, so no need to be picky about child tlists */
 	outer_plan = create_plan_recurse(root, best_path->outerjoinpath, 0);
@@ -3752,38 +3746,10 @@ create_nestloop_plan(PlannerInfo *root,
 
 	/*
 	 * Identify any nestloop parameters that should be supplied by this join
-	 * node, and move them from root->curOuterParams to the nestParams list.
+	 * node, and remove them from root->curOuterParams.
 	 */
 	outerrelids = best_path->outerjoinpath->parent->relids;
-	nestParams = NIL;
-	prev = NULL;
-	for (cell = list_head(root->curOuterParams); cell; cell = next)
-	{
-		NestLoopParam *nlp = (NestLoopParam *) lfirst(cell);
-
-		next = lnext(cell);
-		if (IsA(nlp->paramval, Var) &&
-			bms_is_member(nlp->paramval->varno, outerrelids))
-		{
-			root->curOuterParams = list_delete_cell(root->curOuterParams,
-													cell, prev);
-			nestParams = lappend(nestParams, nlp);
-		}
-		else if (IsA(nlp->paramval, PlaceHolderVar) &&
-				 bms_overlap(((PlaceHolderVar *) nlp->paramval)->phrels,
-							 outerrelids) &&
-				 bms_is_subset(find_placeholder_info(root,
-													 (PlaceHolderVar *) nlp->paramval,
-													 false)->ph_eval_at,
-							   outerrelids))
-		{
-			root->curOuterParams = list_delete_cell(root->curOuterParams,
-													cell, prev);
-			nestParams = lappend(nestParams, nlp);
-		}
-		else
-			prev = cell;
-	}
+	nestParams = identify_current_nestloop_params(root, outerrelids);
 
 	join_plan = make_nestloop(tlist,
 							  joinclauses,
@@ -4283,42 +4249,18 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root)
 	if (IsA(node, Var))
 	{
 		Var		   *var = (Var *) node;
-		Param	   *param;
-		NestLoopParam *nlp;
-		ListCell   *lc;
 
 		/* Upper-level Vars should be long gone at this point */
 		Assert(var->varlevelsup == 0);
 		/* If not to be replaced, we can just return the Var unmodified */
 		if (!bms_is_member(var->varno, root->curOuterRels))
 			return node;
-		/* Create a Param representing the Var */
-		param = assign_nestloop_param_var(root, var);
-		/* Is this param already listed in root->curOuterParams? */
-		foreach(lc, root->curOuterParams)
-		{
-			nlp = (NestLoopParam *) lfirst(lc);
-			if (nlp->paramno == param->paramid)
-			{
-				Assert(equal(var, nlp->paramval));
-				/* Present, so we can just return the Param */
-				return (Node *) param;
-			}
-		}
-		/* No, so add it */
-		nlp = makeNode(NestLoopParam);
-		nlp->paramno = param->paramid;
-		nlp->paramval = var;
-		root->curOuterParams = lappend(root->curOuterParams, nlp);
-		/* And return the replacement Param */
-		return (Node *) param;
+		/* Replace the Var with a nestloop Param */
+		return (Node *) replace_nestloop_param_var(root, var);
 	}
 	if (IsA(node, PlaceHolderVar))
 	{
 		PlaceHolderVar *phv = (PlaceHolderVar *) node;
-		Param	   *param;
-		NestLoopParam *nlp;
-		ListCell   *lc;
 
 		/* Upper-level PlaceHolderVars should be long gone at this point */
 		Assert(phv->phlevelsup == 0);
@@ -4355,118 +4297,14 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root)
 												root);
 			return (Node *) newphv;
 		}
-		/* Create a Param representing the PlaceHolderVar */
-		param = assign_nestloop_param_placeholdervar(root, phv);
-		/* Is this param already listed in root->curOuterParams? */
-		foreach(lc, root->curOuterParams)
-		{
-			nlp = (NestLoopParam *) lfirst(lc);
-			if (nlp->paramno == param->paramid)
-			{
-				Assert(equal(phv, nlp->paramval));
-				/* Present, so we can just return the Param */
-				return (Node *) param;
-			}
-		}
-		/* No, so add it */
-		nlp = makeNode(NestLoopParam);
-		nlp->paramno = param->paramid;
-		nlp->paramval = (Var *) phv;
-		root->curOuterParams = lappend(root->curOuterParams, nlp);
-		/* And return the replacement Param */
-		return (Node *) param;
+		/* Replace the PlaceHolderVar with a nestloop Param */
+		return (Node *) replace_nestloop_param_placeholdervar(root, phv);
 	}
 	return expression_tree_mutator(node,
 								   replace_nestloop_params_mutator,
 								   (void *) root);
 }
 
-/*
- * process_subquery_nestloop_params
- *	  Handle params of a parameterized subquery that need to be fed
- *	  from an outer nestloop.
- *
- * Currently, that would be *all* params that a subquery in FROM has demanded
- * from the current query level, since they must be LATERAL references.
- *
- * The subplan's references to the outer variables are already represented
- * as PARAM_EXEC Params, so we need not modify the subplan here.  What we
- * do need to do is add entries to root->curOuterParams to signal the parent
- * nestloop plan node that it must provide these values.
- */
-static void
-process_subquery_nestloop_params(PlannerInfo *root, List *subplan_params)
-{
-	ListCell   *ppl;
-
-	foreach(ppl, subplan_params)
-	{
-		PlannerParamItem *pitem = (PlannerParamItem *) lfirst(ppl);
-
-		if (IsA(pitem->item, Var))
-		{
-			Var		   *var = (Var *) pitem->item;
-			NestLoopParam *nlp;
-			ListCell   *lc;
-
-			/* If not from a nestloop outer rel, complain */
-			if (!bms_is_member(var->varno, root->curOuterRels))
-				elog(ERROR, "non-LATERAL parameter required by subquery");
-			/* Is this param already listed in root->curOuterParams? */
-			foreach(lc, root->curOuterParams)
-			{
-				nlp = (NestLoopParam *) lfirst(lc);
-				if (nlp->paramno == pitem->paramId)
-				{
-					Assert(equal(var, nlp->paramval));
-					/* Present, so nothing to do */
-					break;
-				}
-			}
-			if (lc == NULL)
-			{
-				/* No, so add it */
-				nlp = makeNode(NestLoopParam);
-				nlp->paramno = pitem->paramId;
-				nlp->paramval = copyObject(var);
-				root->curOuterParams = lappend(root->curOuterParams, nlp);
-			}
-		}
-		else if (IsA(pitem->item, PlaceHolderVar))
-		{
-			PlaceHolderVar *phv = (PlaceHolderVar *) pitem->item;
-			NestLoopParam *nlp;
-			ListCell   *lc;
-
-			/* If not from a nestloop outer rel, complain */
-			if (!bms_is_subset(find_placeholder_info(root, phv, false)->ph_eval_at,
-							   root->curOuterRels))
-				elog(ERROR, "non-LATERAL parameter required by subquery");
-			/* Is this param already listed in root->curOuterParams? */
-			foreach(lc, root->curOuterParams)
-			{
-				nlp = (NestLoopParam *) lfirst(lc);
-				if (nlp->paramno == pitem->paramId)
-				{
-					Assert(equal(phv, nlp->paramval));
-					/* Present, so nothing to do */
-					break;
-				}
-			}
-			if (lc == NULL)
-			{
-				/* No, so add it */
-				nlp = makeNode(NestLoopParam);
-				nlp->paramno = pitem->paramId;
-				nlp->paramval = (Var *) copyObject(phv);
-				root->curOuterParams = lappend(root->curOuterParams, nlp);
-			}
-		}
-		else
-			elog(ERROR, "unexpected type of subquery parameter");
-	}
-}
-
 /*
  * fix_indexqual_references
  *	  Adjust indexqual clauses to the form the executor's indexqual
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index a85e2afa5fe81be87ad85be5311911ad74754ca8..7f1f962f60ad0e583f24cba46f25ca74db33f778 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -39,6 +39,7 @@
 #endif
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/paramassign.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/plancat.h"
@@ -625,7 +626,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	root->inhTargetKind = INHKIND_NONE;
 	root->hasRecursion = hasRecursion;
 	if (hasRecursion)
-		root->wt_param_id = SS_assign_special_param(root);
+		root->wt_param_id = assign_special_exec_param(root);
 	else
 		root->wt_param_id = -1;
 	root->non_recursive_path = NULL;
@@ -1640,7 +1641,7 @@ inheritance_planner(PlannerInfo *root)
 									 returningLists,
 									 rowMarks,
 									 NULL,
-									 SS_assign_special_param(root)));
+									 assign_special_exec_param(root)));
 }
 
 /*--------------------
@@ -2155,7 +2156,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 		{
 			path = (Path *) create_lockrows_path(root, final_rel, path,
 												 root->rowMarks,
-												 SS_assign_special_param(root));
+												 assign_special_exec_param(root));
 		}
 
 		/*
@@ -2217,7 +2218,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 										returningLists,
 										rowMarks,
 										parse->onConflict,
-										SS_assign_special_param(root));
+										assign_special_exec_param(root));
 		}
 
 		/* And shove it into final_rel */
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 83008d76619e52d4fad5a2f211e622ce30b4b026..d28323f03c327b6796a7127735afac6fad85a7db 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1,7 +1,10 @@
 /*-------------------------------------------------------------------------
  *
  * subselect.c
- *	  Planning routines for subselects and parameters.
+ *	  Planning routines for subselects.
+ *
+ * This module deals with SubLinks and CTEs, but not subquery RTEs (i.e.,
+ * not sub-SELECT-in-FROM cases).
  *
  * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -22,6 +25,7 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/paramassign.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
@@ -86,337 +90,6 @@ static bool finalize_primnode(Node *node, finalize_primnode_context *context);
 static bool finalize_agg_primnode(Node *node, finalize_primnode_context *context);
 
 
-/*
- * Select a PARAM_EXEC number to identify the given Var as a parameter for
- * the current subquery, or for a nestloop's inner scan.
- * If the Var already has a param in the current context, return that one.
- */
-static int
-assign_param_for_var(PlannerInfo *root, Var *var)
-{
-	ListCell   *ppl;
-	PlannerParamItem *pitem;
-	Index		levelsup;
-
-	/* Find the query level the Var belongs to */
-	for (levelsup = var->varlevelsup; levelsup > 0; levelsup--)
-		root = root->parent_root;
-
-	/* If there's already a matching PlannerParamItem there, just use it */
-	foreach(ppl, root->plan_params)
-	{
-		pitem = (PlannerParamItem *) lfirst(ppl);
-		if (IsA(pitem->item, Var))
-		{
-			Var		   *pvar = (Var *) pitem->item;
-
-			/*
-			 * This comparison must match _equalVar(), except for ignoring
-			 * varlevelsup.  Note that _equalVar() ignores the location.
-			 */
-			if (pvar->varno == var->varno &&
-				pvar->varattno == var->varattno &&
-				pvar->vartype == var->vartype &&
-				pvar->vartypmod == var->vartypmod &&
-				pvar->varcollid == var->varcollid &&
-				pvar->varnoold == var->varnoold &&
-				pvar->varoattno == var->varoattno)
-				return pitem->paramId;
-		}
-	}
-
-	/* Nope, so make a new one */
-	var = copyObject(var);
-	var->varlevelsup = 0;
-
-	pitem = makeNode(PlannerParamItem);
-	pitem->item = (Node *) var;
-	pitem->paramId = list_length(root->glob->paramExecTypes);
-	root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes,
-											 var->vartype);
-
-	root->plan_params = lappend(root->plan_params, pitem);
-
-	return pitem->paramId;
-}
-
-/*
- * Generate a Param node to replace the given Var,
- * which is expected to have varlevelsup > 0 (ie, it is not local).
- */
-static Param *
-replace_outer_var(PlannerInfo *root, Var *var)
-{
-	Param	   *retval;
-	int			i;
-
-	Assert(var->varlevelsup > 0 && var->varlevelsup < root->query_level);
-
-	/* Find the Var in the appropriate plan_params, or add it if not present */
-	i = assign_param_for_var(root, var);
-
-	retval = makeNode(Param);
-	retval->paramkind = PARAM_EXEC;
-	retval->paramid = i;
-	retval->paramtype = var->vartype;
-	retval->paramtypmod = var->vartypmod;
-	retval->paramcollid = var->varcollid;
-	retval->location = var->location;
-
-	return retval;
-}
-
-/*
- * Generate a Param node to replace the given Var, which will be supplied
- * from an upper NestLoop join node.
- *
- * This is effectively the same as replace_outer_var, except that we expect
- * the Var to be local to the current query level.
- */
-Param *
-assign_nestloop_param_var(PlannerInfo *root, Var *var)
-{
-	Param	   *retval;
-	int			i;
-
-	Assert(var->varlevelsup == 0);
-
-	i = assign_param_for_var(root, var);
-
-	retval = makeNode(Param);
-	retval->paramkind = PARAM_EXEC;
-	retval->paramid = i;
-	retval->paramtype = var->vartype;
-	retval->paramtypmod = var->vartypmod;
-	retval->paramcollid = var->varcollid;
-	retval->location = var->location;
-
-	return retval;
-}
-
-/*
- * Select a PARAM_EXEC number to identify the given PlaceHolderVar as a
- * parameter for the current subquery, or for a nestloop's inner scan.
- * If the PHV already has a param in the current context, return that one.
- *
- * This is just like assign_param_for_var, except for PlaceHolderVars.
- */
-static int
-assign_param_for_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv)
-{
-	ListCell   *ppl;
-	PlannerParamItem *pitem;
-	Index		levelsup;
-
-	/* Find the query level the PHV belongs to */
-	for (levelsup = phv->phlevelsup; levelsup > 0; levelsup--)
-		root = root->parent_root;
-
-	/* If there's already a matching PlannerParamItem there, just use it */
-	foreach(ppl, root->plan_params)
-	{
-		pitem = (PlannerParamItem *) lfirst(ppl);
-		if (IsA(pitem->item, PlaceHolderVar))
-		{
-			PlaceHolderVar *pphv = (PlaceHolderVar *) pitem->item;
-
-			/* We assume comparing the PHIDs is sufficient */
-			if (pphv->phid == phv->phid)
-				return pitem->paramId;
-		}
-	}
-
-	/* Nope, so make a new one */
-	phv = copyObject(phv);
-	if (phv->phlevelsup != 0)
-	{
-		IncrementVarSublevelsUp((Node *) phv, -((int) phv->phlevelsup), 0);
-		Assert(phv->phlevelsup == 0);
-	}
-
-	pitem = makeNode(PlannerParamItem);
-	pitem->item = (Node *) phv;
-	pitem->paramId = list_length(root->glob->paramExecTypes);
-	root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes,
-											 exprType((Node *) phv->phexpr));
-
-	root->plan_params = lappend(root->plan_params, pitem);
-
-	return pitem->paramId;
-}
-
-/*
- * Generate a Param node to replace the given PlaceHolderVar,
- * which is expected to have phlevelsup > 0 (ie, it is not local).
- *
- * This is just like replace_outer_var, except for PlaceHolderVars.
- */
-static Param *
-replace_outer_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv)
-{
-	Param	   *retval;
-	int			i;
-
-	Assert(phv->phlevelsup > 0 && phv->phlevelsup < root->query_level);
-
-	/* Find the PHV in the appropriate plan_params, or add it if not present */
-	i = assign_param_for_placeholdervar(root, phv);
-
-	retval = makeNode(Param);
-	retval->paramkind = PARAM_EXEC;
-	retval->paramid = i;
-	retval->paramtype = exprType((Node *) phv->phexpr);
-	retval->paramtypmod = exprTypmod((Node *) phv->phexpr);
-	retval->paramcollid = exprCollation((Node *) phv->phexpr);
-	retval->location = -1;
-
-	return retval;
-}
-
-/*
- * Generate a Param node to replace the given PlaceHolderVar, which will be
- * supplied from an upper NestLoop join node.
- *
- * This is just like assign_nestloop_param_var, except for PlaceHolderVars.
- */
-Param *
-assign_nestloop_param_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv)
-{
-	Param	   *retval;
-	int			i;
-
-	Assert(phv->phlevelsup == 0);
-
-	i = assign_param_for_placeholdervar(root, phv);
-
-	retval = makeNode(Param);
-	retval->paramkind = PARAM_EXEC;
-	retval->paramid = i;
-	retval->paramtype = exprType((Node *) phv->phexpr);
-	retval->paramtypmod = exprTypmod((Node *) phv->phexpr);
-	retval->paramcollid = exprCollation((Node *) phv->phexpr);
-	retval->location = -1;
-
-	return retval;
-}
-
-/*
- * Generate a Param node to replace the given Aggref
- * which is expected to have agglevelsup > 0 (ie, it is not local).
- */
-static Param *
-replace_outer_agg(PlannerInfo *root, Aggref *agg)
-{
-	Param	   *retval;
-	PlannerParamItem *pitem;
-	Index		levelsup;
-
-	Assert(agg->agglevelsup > 0 && agg->agglevelsup < root->query_level);
-
-	/* Find the query level the Aggref belongs to */
-	for (levelsup = agg->agglevelsup; levelsup > 0; levelsup--)
-		root = root->parent_root;
-
-	/*
-	 * It does not seem worthwhile to try to match duplicate outer aggs. Just
-	 * make a new slot every time.
-	 */
-	agg = copyObject(agg);
-	IncrementVarSublevelsUp((Node *) agg, -((int) agg->agglevelsup), 0);
-	Assert(agg->agglevelsup == 0);
-
-	pitem = makeNode(PlannerParamItem);
-	pitem->item = (Node *) agg;
-	pitem->paramId = list_length(root->glob->paramExecTypes);
-	root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes,
-											 agg->aggtype);
-
-	root->plan_params = lappend(root->plan_params, pitem);
-
-	retval = makeNode(Param);
-	retval->paramkind = PARAM_EXEC;
-	retval->paramid = pitem->paramId;
-	retval->paramtype = agg->aggtype;
-	retval->paramtypmod = -1;
-	retval->paramcollid = agg->aggcollid;
-	retval->location = agg->location;
-
-	return retval;
-}
-
-/*
- * Generate a Param node to replace the given GroupingFunc expression which is
- * expected to have agglevelsup > 0 (ie, it is not local).
- */
-static Param *
-replace_outer_grouping(PlannerInfo *root, GroupingFunc *grp)
-{
-	Param	   *retval;
-	PlannerParamItem *pitem;
-	Index		levelsup;
-	Oid			ptype;
-
-	Assert(grp->agglevelsup > 0 && grp->agglevelsup < root->query_level);
-
-	/* Find the query level the GroupingFunc belongs to */
-	for (levelsup = grp->agglevelsup; levelsup > 0; levelsup--)
-		root = root->parent_root;
-
-	/*
-	 * It does not seem worthwhile to try to match duplicate outer aggs. Just
-	 * make a new slot every time.
-	 */
-	grp = copyObject(grp);
-	IncrementVarSublevelsUp((Node *) grp, -((int) grp->agglevelsup), 0);
-	Assert(grp->agglevelsup == 0);
-	ptype = exprType((Node *) grp);
-
-	pitem = makeNode(PlannerParamItem);
-	pitem->item = (Node *) grp;
-	pitem->paramId = list_length(root->glob->paramExecTypes);
-	root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes,
-											 ptype);
-
-	root->plan_params = lappend(root->plan_params, pitem);
-
-	retval = makeNode(Param);
-	retval->paramkind = PARAM_EXEC;
-	retval->paramid = pitem->paramId;
-	retval->paramtype = ptype;
-	retval->paramtypmod = -1;
-	retval->paramcollid = InvalidOid;
-	retval->location = grp->location;
-
-	return retval;
-}
-
-/*
- * Generate a new Param node that will not conflict with any other.
- *
- * This is used to create Params representing subplan outputs.
- * We don't need to build a PlannerParamItem for such a Param, but we do
- * need to make sure we record the type in paramExecTypes (otherwise,
- * there won't be a slot allocated for it).
- */
-static Param *
-generate_new_param(PlannerInfo *root, Oid paramtype, int32 paramtypmod,
-				   Oid paramcollation)
-{
-	Param	   *retval;
-
-	retval = makeNode(Param);
-	retval->paramkind = PARAM_EXEC;
-	retval->paramid = list_length(root->glob->paramExecTypes);
-	root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes,
-											 paramtype);
-	retval->paramtype = paramtype;
-	retval->paramtypmod = paramtypmod;
-	retval->paramcollid = paramcollation;
-	retval->location = -1;
-
-	return retval;
-}
-
 /*
  * Assign a (nonnegative) PARAM_EXEC ID for a special parameter (one that
  * is not actually used to carry a value at runtime).  Such parameters are
@@ -424,15 +97,14 @@ generate_new_param(PlannerInfo *root, Oid paramtype, int32 paramtypmod,
  * recursive union node to its worktable scan node or forcing plan
  * re-evaluation within the EvalPlanQual mechanism.  No actual Param node
  * exists with this ID, however.
+ *
+ * XXX deprecated: use assign_special_exec_param directly, instead.  We are
+ * keeping this in v11 and below only to avoid API breaks.
  */
 int
 SS_assign_special_param(PlannerInfo *root)
 {
-	int			paramId = list_length(root->glob->paramExecTypes);
-
-	root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes,
-											 InvalidOid);
-	return paramId;
+	return assign_special_exec_param(root);
 }
 
 /*
@@ -716,7 +388,7 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
 		Param	   *prm;
 
 		Assert(testexpr == NULL);
-		prm = generate_new_param(root, BOOLOID, -1, InvalidOid);
+		prm = generate_new_exec_param(root, BOOLOID, -1, InvalidOid);
 		splan->setParam = list_make1_int(prm->paramid);
 		isInitPlan = true;
 		result = (Node *) prm;
@@ -728,10 +400,10 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
 
 		Assert(!te->resjunk);
 		Assert(testexpr == NULL);
-		prm = generate_new_param(root,
-								 exprType((Node *) te->expr),
-								 exprTypmod((Node *) te->expr),
-								 exprCollation((Node *) te->expr));
+		prm = generate_new_exec_param(root,
+									  exprType((Node *) te->expr),
+									  exprTypmod((Node *) te->expr),
+									  exprCollation((Node *) te->expr));
 		splan->setParam = list_make1_int(prm->paramid);
 		isInitPlan = true;
 		result = (Node *) prm;
@@ -748,10 +420,10 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
 		if (!OidIsValid(arraytype))
 			elog(ERROR, "could not find array type for datatype %s",
 				 format_type_be(exprType((Node *) te->expr)));
-		prm = generate_new_param(root,
-								 arraytype,
-								 exprTypmod((Node *) te->expr),
-								 exprCollation((Node *) te->expr));
+		prm = generate_new_exec_param(root,
+									  arraytype,
+									  exprTypmod((Node *) te->expr),
+									  exprCollation((Node *) te->expr));
 		splan->setParam = list_make1_int(prm->paramid);
 		isInitPlan = true;
 		result = (Node *) prm;
@@ -930,10 +602,10 @@ generate_subquery_params(PlannerInfo *root, List *tlist, List **paramIds)
 		if (tent->resjunk)
 			continue;
 
-		param = generate_new_param(root,
-								   exprType((Node *) tent->expr),
-								   exprTypmod((Node *) tent->expr),
-								   exprCollation((Node *) tent->expr));
+		param = generate_new_exec_param(root,
+										exprType((Node *) tent->expr),
+										exprTypmod((Node *) tent->expr),
+										exprCollation((Node *) tent->expr));
 		result = lappend(result, param);
 		ids = lappend_int(ids, param->paramid);
 	}
@@ -1257,7 +929,7 @@ SS_process_ctes(PlannerInfo *root)
 		 * ParamExecData slot for this param ID for communication among
 		 * multiple CteScan nodes that might be scanning this CTE.)
 		 */
-		paramid = SS_assign_special_param(root);
+		paramid = assign_special_exec_param(root);
 		splan->setParam = list_make1_int(paramid);
 
 		/*
@@ -1863,10 +1535,10 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect,
 		Param	   *param;
 
 		cc = lnext(cc);
-		param = generate_new_param(root,
-								   exprType(rightarg),
-								   exprTypmod(rightarg),
-								   exprCollation(rightarg));
+		param = generate_new_exec_param(root,
+										exprType(rightarg),
+										exprTypmod(rightarg),
+										exprCollation(rightarg));
 		tlist = lappend(tlist,
 						makeTargetEntry((Expr *) rightarg,
 										resno++,
@@ -2917,7 +2589,7 @@ finalize_primnode(Node *node, finalize_primnode_context *context)
 		 * parameter change signaling since we always re-evaluate the subplan.
 		 * Note that this wouldn't work too well if there might be uses of the
 		 * same param IDs elsewhere in the plan, but that can't happen because
-		 * generate_new_param never tries to merge params.
+		 * generate_new_exec_param never tries to merge params.
 		 */
 		foreach(lc, subplan->paramIds)
 		{
@@ -2983,7 +2655,8 @@ SS_make_initplan_output_param(PlannerInfo *root,
 							  Oid resulttype, int32 resulttypmod,
 							  Oid resultcollation)
 {
-	return generate_new_param(root, resulttype, resulttypmod, resultcollation);
+	return generate_new_exec_param(root, resulttype,
+								   resulttypmod, resultcollation);
 }
 
 /*
diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile
index c54d0a690d85a9004b4877af6f283c1a732a47b5..87f22bbb004c9b8d64c8ff9d6ccff857713ae3eb 100644
--- a/src/backend/optimizer/util/Makefile
+++ b/src/backend/optimizer/util/Makefile
@@ -12,7 +12,8 @@ subdir = src/backend/optimizer/util
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = clauses.o joininfo.o orclauses.o pathnode.o placeholder.o \
+OBJS = clauses.o joininfo.o orclauses.o \
+       paramassign.o pathnode.o placeholder.o \
        plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/util/paramassign.c b/src/backend/optimizer/util/paramassign.c
new file mode 100644
index 0000000000000000000000000000000000000000..838587c2b8ddd49af4fba40d19d219b41efc9a19
--- /dev/null
+++ b/src/backend/optimizer/util/paramassign.c
@@ -0,0 +1,599 @@
+/*-------------------------------------------------------------------------
+ *
+ * paramassign.c
+ *		Functions for assigning PARAM_EXEC slots during planning.
+ *
+ * This module is responsible for managing three planner data structures:
+ *
+ * root->glob->paramExecTypes: records actual assignments of PARAM_EXEC slots.
+ * The i'th list element holds the data type OID of the i'th parameter slot.
+ * (Elements can be InvalidOid if they represent slots that are needed for
+ * chgParam signaling, but will never hold a value at runtime.)  This list is
+ * global to the whole plan since the executor has only one PARAM_EXEC array.
+ * Assignments are permanent for the plan: we never remove entries once added.
+ *
+ * root->plan_params: a list of PlannerParamItem nodes, recording Vars and
+ * PlaceHolderVars that the root's query level needs to supply to lower-level
+ * subqueries, along with the PARAM_EXEC number to use for each such value.
+ * Elements are added to this list while planning a subquery, and the list
+ * is reset to empty after completion of each subquery.
+ *
+ * root->curOuterParams: a list of NestLoopParam nodes, recording Vars and
+ * PlaceHolderVars that some outer level of nestloop needs to pass down to
+ * a lower-level plan node in its righthand side.  Elements are added to this
+ * list as createplan.c creates lower Plan nodes that need such Params, and
+ * are removed when it creates a NestLoop Plan node that will supply those
+ * values.
+ *
+ * The latter two data structures are used to prevent creating multiple
+ * PARAM_EXEC slots (each requiring work to fill) when the same upper
+ * SubPlan or NestLoop supplies a value that is referenced in more than
+ * one place in its child plan nodes.  However, when the same Var has to
+ * be supplied to different subplan trees by different SubPlan or NestLoop
+ * parent nodes, we don't recognize any commonality; a fresh plan_params or
+ * curOuterParams entry will be made (since the old one has been removed
+ * when we finished processing the earlier SubPlan or NestLoop) and a fresh
+ * PARAM_EXEC number will be assigned.  At one time we tried to avoid
+ * allocating duplicate PARAM_EXEC numbers in such cases, but it's harder
+ * than it seems to avoid bugs due to overlapping Param lifetimes, so we
+ * don't risk that anymore.  Minimizing the number of PARAM_EXEC slots
+ * doesn't really save much executor work anyway.
+ *
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/optimizer/util/paramassign.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/nodeFuncs.h"
+#include "nodes/plannodes.h"
+#include "optimizer/paramassign.h"
+#include "optimizer/placeholder.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * Select a PARAM_EXEC number to identify the given Var as a parameter for
+ * the current subquery.  (It might already have one.)
+ * Record the need for the Var in the proper upper-level root->plan_params.
+ */
+static int
+assign_param_for_var(PlannerInfo *root, Var *var)
+{
+	ListCell   *ppl;
+	PlannerParamItem *pitem;
+	Index		levelsup;
+
+	/* Find the query level the Var belongs to */
+	for (levelsup = var->varlevelsup; levelsup > 0; levelsup--)
+		root = root->parent_root;
+
+	/* If there's already a matching PlannerParamItem there, just use it */
+	foreach(ppl, root->plan_params)
+	{
+		pitem = (PlannerParamItem *) lfirst(ppl);
+		if (IsA(pitem->item, Var))
+		{
+			Var		   *pvar = (Var *) pitem->item;
+
+			/*
+			 * This comparison must match _equalVar(), except for ignoring
+			 * varlevelsup.  Note that _equalVar() ignores the location.
+			 */
+			if (pvar->varno == var->varno &&
+				pvar->varattno == var->varattno &&
+				pvar->vartype == var->vartype &&
+				pvar->vartypmod == var->vartypmod &&
+				pvar->varcollid == var->varcollid &&
+				pvar->varnoold == var->varnoold &&
+				pvar->varoattno == var->varoattno)
+				return pitem->paramId;
+		}
+	}
+
+	/* Nope, so make a new one */
+	var = copyObject(var);
+	var->varlevelsup = 0;
+
+	pitem = makeNode(PlannerParamItem);
+	pitem->item = (Node *) var;
+	pitem->paramId = list_length(root->glob->paramExecTypes);
+	root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes,
+											 var->vartype);
+
+	root->plan_params = lappend(root->plan_params, pitem);
+
+	return pitem->paramId;
+}
+
+/*
+ * Generate a Param node to replace the given Var,
+ * which is expected to have varlevelsup > 0 (ie, it is not local).
+ * Record the need for the Var in the proper upper-level root->plan_params.
+ */
+Param *
+replace_outer_var(PlannerInfo *root, Var *var)
+{
+	Param	   *retval;
+	int			i;
+
+	Assert(var->varlevelsup > 0 && var->varlevelsup < root->query_level);
+
+	/* Find the Var in the appropriate plan_params, or add it if not present */
+	i = assign_param_for_var(root, var);
+
+	retval = makeNode(Param);
+	retval->paramkind = PARAM_EXEC;
+	retval->paramid = i;
+	retval->paramtype = var->vartype;
+	retval->paramtypmod = var->vartypmod;
+	retval->paramcollid = var->varcollid;
+	retval->location = var->location;
+
+	return retval;
+}
+
+/*
+ * Select a PARAM_EXEC number to identify the given PlaceHolderVar as a
+ * parameter for the current subquery.  (It might already have one.)
+ * Record the need for the PHV in the proper upper-level root->plan_params.
+ *
+ * This is just like assign_param_for_var, except for PlaceHolderVars.
+ */
+static int
+assign_param_for_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv)
+{
+	ListCell   *ppl;
+	PlannerParamItem *pitem;
+	Index		levelsup;
+
+	/* Find the query level the PHV belongs to */
+	for (levelsup = phv->phlevelsup; levelsup > 0; levelsup--)
+		root = root->parent_root;
+
+	/* If there's already a matching PlannerParamItem there, just use it */
+	foreach(ppl, root->plan_params)
+	{
+		pitem = (PlannerParamItem *) lfirst(ppl);
+		if (IsA(pitem->item, PlaceHolderVar))
+		{
+			PlaceHolderVar *pphv = (PlaceHolderVar *) pitem->item;
+
+			/* We assume comparing the PHIDs is sufficient */
+			if (pphv->phid == phv->phid)
+				return pitem->paramId;
+		}
+	}
+
+	/* Nope, so make a new one */
+	phv = copyObject(phv);
+	IncrementVarSublevelsUp((Node *) phv, -((int) phv->phlevelsup), 0);
+	Assert(phv->phlevelsup == 0);
+
+	pitem = makeNode(PlannerParamItem);
+	pitem->item = (Node *) phv;
+	pitem->paramId = list_length(root->glob->paramExecTypes);
+	root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes,
+											 exprType((Node *) phv->phexpr));
+
+	root->plan_params = lappend(root->plan_params, pitem);
+
+	return pitem->paramId;
+}
+
+/*
+ * Generate a Param node to replace the given PlaceHolderVar,
+ * which is expected to have phlevelsup > 0 (ie, it is not local).
+ * Record the need for the PHV in the proper upper-level root->plan_params.
+ *
+ * This is just like replace_outer_var, except for PlaceHolderVars.
+ */
+Param *
+replace_outer_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv)
+{
+	Param	   *retval;
+	int			i;
+
+	Assert(phv->phlevelsup > 0 && phv->phlevelsup < root->query_level);
+
+	/* Find the PHV in the appropriate plan_params, or add it if not present */
+	i = assign_param_for_placeholdervar(root, phv);
+
+	retval = makeNode(Param);
+	retval->paramkind = PARAM_EXEC;
+	retval->paramid = i;
+	retval->paramtype = exprType((Node *) phv->phexpr);
+	retval->paramtypmod = exprTypmod((Node *) phv->phexpr);
+	retval->paramcollid = exprCollation((Node *) phv->phexpr);
+	retval->location = -1;
+
+	return retval;
+}
+
+/*
+ * Generate a Param node to replace the given Aggref
+ * which is expected to have agglevelsup > 0 (ie, it is not local).
+ * Record the need for the Aggref in the proper upper-level root->plan_params.
+ */
+Param *
+replace_outer_agg(PlannerInfo *root, Aggref *agg)
+{
+	Param	   *retval;
+	PlannerParamItem *pitem;
+	Index		levelsup;
+
+	Assert(agg->agglevelsup > 0 && agg->agglevelsup < root->query_level);
+
+	/* Find the query level the Aggref belongs to */
+	for (levelsup = agg->agglevelsup; levelsup > 0; levelsup--)
+		root = root->parent_root;
+
+	/*
+	 * It does not seem worthwhile to try to de-duplicate references to outer
+	 * aggs.  Just make a new slot every time.
+	 */
+	agg = copyObject(agg);
+	IncrementVarSublevelsUp((Node *) agg, -((int) agg->agglevelsup), 0);
+	Assert(agg->agglevelsup == 0);
+
+	pitem = makeNode(PlannerParamItem);
+	pitem->item = (Node *) agg;
+	pitem->paramId = list_length(root->glob->paramExecTypes);
+	root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes,
+											 agg->aggtype);
+
+	root->plan_params = lappend(root->plan_params, pitem);
+
+	retval = makeNode(Param);
+	retval->paramkind = PARAM_EXEC;
+	retval->paramid = pitem->paramId;
+	retval->paramtype = agg->aggtype;
+	retval->paramtypmod = -1;
+	retval->paramcollid = agg->aggcollid;
+	retval->location = agg->location;
+
+	return retval;
+}
+
+/*
+ * Generate a Param node to replace the given GroupingFunc expression which is
+ * expected to have agglevelsup > 0 (ie, it is not local).
+ * Record the need for the GroupingFunc in the proper upper-level
+ * root->plan_params.
+ */
+Param *
+replace_outer_grouping(PlannerInfo *root, GroupingFunc *grp)
+{
+	Param	   *retval;
+	PlannerParamItem *pitem;
+	Index		levelsup;
+	Oid			ptype = exprType((Node *) grp);
+
+	Assert(grp->agglevelsup > 0 && grp->agglevelsup < root->query_level);
+
+	/* Find the query level the GroupingFunc belongs to */
+	for (levelsup = grp->agglevelsup; levelsup > 0; levelsup--)
+		root = root->parent_root;
+
+	/*
+	 * It does not seem worthwhile to try to de-duplicate references to outer
+	 * aggs.  Just make a new slot every time.
+	 */
+	grp = copyObject(grp);
+	IncrementVarSublevelsUp((Node *) grp, -((int) grp->agglevelsup), 0);
+	Assert(grp->agglevelsup == 0);
+
+	pitem = makeNode(PlannerParamItem);
+	pitem->item = (Node *) grp;
+	pitem->paramId = list_length(root->glob->paramExecTypes);
+	root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes,
+											 ptype);
+
+	root->plan_params = lappend(root->plan_params, pitem);
+
+	retval = makeNode(Param);
+	retval->paramkind = PARAM_EXEC;
+	retval->paramid = pitem->paramId;
+	retval->paramtype = ptype;
+	retval->paramtypmod = -1;
+	retval->paramcollid = InvalidOid;
+	retval->location = grp->location;
+
+	return retval;
+}
+
+/*
+ * Generate a Param node to replace the given Var,
+ * which is expected to come from some upper NestLoop plan node.
+ * Record the need for the Var in root->curOuterParams.
+ */
+Param *
+replace_nestloop_param_var(PlannerInfo *root, Var *var)
+{
+	Param	   *param;
+	NestLoopParam *nlp;
+	ListCell   *lc;
+
+	/* Is this Var already listed in root->curOuterParams? */
+	foreach(lc, root->curOuterParams)
+	{
+		nlp = (NestLoopParam *) lfirst(lc);
+		if (equal(var, nlp->paramval))
+		{
+			/* Yes, so just make a Param referencing this NLP's slot */
+			param = makeNode(Param);
+			param->paramkind = PARAM_EXEC;
+			param->paramid = nlp->paramno;
+			param->paramtype = var->vartype;
+			param->paramtypmod = var->vartypmod;
+			param->paramcollid = var->varcollid;
+			param->location = var->location;
+			return param;
+		}
+	}
+
+	/* No, so assign a PARAM_EXEC slot for a new NLP */
+	param = generate_new_exec_param(root,
+									var->vartype,
+									var->vartypmod,
+									var->varcollid);
+	param->location = var->location;
+
+	/* Add it to the list of required NLPs */
+	nlp = makeNode(NestLoopParam);
+	nlp->paramno = param->paramid;
+	nlp->paramval = copyObject(var);
+	root->curOuterParams = lappend(root->curOuterParams, nlp);
+
+	/* And return the replacement Param */
+	return param;
+}
+
+/*
+ * Generate a Param node to replace the given PlaceHolderVar,
+ * which is expected to come from some upper NestLoop plan node.
+ * Record the need for the PHV in root->curOuterParams.
+ *
+ * This is just like replace_nestloop_param_var, except for PlaceHolderVars.
+ */
+Param *
+replace_nestloop_param_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv)
+{
+	Param	   *param;
+	NestLoopParam *nlp;
+	ListCell   *lc;
+
+	/* Is this PHV already listed in root->curOuterParams? */
+	foreach(lc, root->curOuterParams)
+	{
+		nlp = (NestLoopParam *) lfirst(lc);
+		if (equal(phv, nlp->paramval))
+		{
+			/* Yes, so just make a Param referencing this NLP's slot */
+			param = makeNode(Param);
+			param->paramkind = PARAM_EXEC;
+			param->paramid = nlp->paramno;
+			param->paramtype = exprType((Node *) phv->phexpr);
+			param->paramtypmod = exprTypmod((Node *) phv->phexpr);
+			param->paramcollid = exprCollation((Node *) phv->phexpr);
+			param->location = -1;
+			return param;
+		}
+	}
+
+	/* No, so assign a PARAM_EXEC slot for a new NLP */
+	param = generate_new_exec_param(root,
+									exprType((Node *) phv->phexpr),
+									exprTypmod((Node *) phv->phexpr),
+									exprCollation((Node *) phv->phexpr));
+
+	/* Add it to the list of required NLPs */
+	nlp = makeNode(NestLoopParam);
+	nlp->paramno = param->paramid;
+	nlp->paramval = (Var *) copyObject(phv);
+	root->curOuterParams = lappend(root->curOuterParams, nlp);
+
+	/* And return the replacement Param */
+	return param;
+}
+
+/*
+ * process_subquery_nestloop_params
+ *	  Handle params of a parameterized subquery that need to be fed
+ *	  from an outer nestloop.
+ *
+ * Currently, that would be *all* params that a subquery in FROM has demanded
+ * from the current query level, since they must be LATERAL references.
+ *
+ * subplan_params is a list of PlannerParamItems that we intend to pass to
+ * a subquery-in-FROM.  (This was constructed in root->plan_params while
+ * planning the subquery, but isn't there anymore when this is called.)
+ *
+ * The subplan's references to the outer variables are already represented
+ * as PARAM_EXEC Params, since that conversion was done by the routines above
+ * while planning the subquery.  So we need not modify the subplan or the
+ * PlannerParamItems here.  What we do need to do is add entries to
+ * root->curOuterParams to signal the parent nestloop plan node that it must
+ * provide these values.  This differs from replace_nestloop_param_var in
+ * that the PARAM_EXEC slots to use have already been determined.
+ *
+ * Note that we also use root->curOuterRels as an implicit parameter for
+ * sanity checks.
+ */
+void
+process_subquery_nestloop_params(PlannerInfo *root, List *subplan_params)
+{
+	ListCell   *lc;
+
+	foreach(lc, subplan_params)
+	{
+		PlannerParamItem *pitem = castNode(PlannerParamItem, lfirst(lc));
+
+		if (IsA(pitem->item, Var))
+		{
+			Var		   *var = (Var *) pitem->item;
+			NestLoopParam *nlp;
+			ListCell   *lc;
+
+			/* If not from a nestloop outer rel, complain */
+			if (!bms_is_member(var->varno, root->curOuterRels))
+				elog(ERROR, "non-LATERAL parameter required by subquery");
+
+			/* Is this param already listed in root->curOuterParams? */
+			foreach(lc, root->curOuterParams)
+			{
+				nlp = (NestLoopParam *) lfirst(lc);
+				if (nlp->paramno == pitem->paramId)
+				{
+					Assert(equal(var, nlp->paramval));
+					/* Present, so nothing to do */
+					break;
+				}
+			}
+			if (lc == NULL)
+			{
+				/* No, so add it */
+				nlp = makeNode(NestLoopParam);
+				nlp->paramno = pitem->paramId;
+				nlp->paramval = copyObject(var);
+				root->curOuterParams = lappend(root->curOuterParams, nlp);
+			}
+		}
+		else if (IsA(pitem->item, PlaceHolderVar))
+		{
+			PlaceHolderVar *phv = (PlaceHolderVar *) pitem->item;
+			NestLoopParam *nlp;
+			ListCell   *lc;
+
+			/* If not from a nestloop outer rel, complain */
+			if (!bms_is_subset(find_placeholder_info(root, phv, false)->ph_eval_at,
+							   root->curOuterRels))
+				elog(ERROR, "non-LATERAL parameter required by subquery");
+
+			/* Is this param already listed in root->curOuterParams? */
+			foreach(lc, root->curOuterParams)
+			{
+				nlp = (NestLoopParam *) lfirst(lc);
+				if (nlp->paramno == pitem->paramId)
+				{
+					Assert(equal(phv, nlp->paramval));
+					/* Present, so nothing to do */
+					break;
+				}
+			}
+			if (lc == NULL)
+			{
+				/* No, so add it */
+				nlp = makeNode(NestLoopParam);
+				nlp->paramno = pitem->paramId;
+				nlp->paramval = (Var *) copyObject(phv);
+				root->curOuterParams = lappend(root->curOuterParams, nlp);
+			}
+		}
+		else
+			elog(ERROR, "unexpected type of subquery parameter");
+	}
+}
+
+/*
+ * Identify any NestLoopParams that should be supplied by a NestLoop plan
+ * node with the specified lefthand rels.  Remove them from the active
+ * root->curOuterParams list and return them as the result list.
+ */
+List *
+identify_current_nestloop_params(PlannerInfo *root, Relids leftrelids)
+{
+	List	   *result;
+	ListCell   *cell;
+	ListCell   *prev;
+	ListCell   *next;
+
+	result = NIL;
+	prev = NULL;
+	for (cell = list_head(root->curOuterParams); cell; cell = next)
+	{
+		NestLoopParam *nlp = (NestLoopParam *) lfirst(cell);
+
+		next = lnext(cell);
+
+		/*
+		 * We are looking for Vars and PHVs that can be supplied by the
+		 * lefthand rels.  The "bms_overlap" test is just an optimization to
+		 * allow skipping find_placeholder_info() if the PHV couldn't match.
+		 */
+		if (IsA(nlp->paramval, Var) &&
+			bms_is_member(nlp->paramval->varno, leftrelids))
+		{
+			root->curOuterParams = list_delete_cell(root->curOuterParams,
+													cell, prev);
+			result = lappend(result, nlp);
+		}
+		else if (IsA(nlp->paramval, PlaceHolderVar) &&
+				 bms_overlap(((PlaceHolderVar *) nlp->paramval)->phrels,
+							 leftrelids) &&
+				 bms_is_subset(find_placeholder_info(root,
+													 (PlaceHolderVar *) nlp->paramval,
+													 false)->ph_eval_at,
+							   leftrelids))
+		{
+			root->curOuterParams = list_delete_cell(root->curOuterParams,
+													cell, prev);
+			result = lappend(result, nlp);
+		}
+		else
+			prev = cell;
+	}
+	return result;
+}
+
+/*
+ * Generate a new Param node that will not conflict with any other.
+ *
+ * This is used to create Params representing subplan outputs or
+ * NestLoop parameters.
+ *
+ * We don't need to build a PlannerParamItem for such a Param, but we do
+ * need to make sure we record the type in paramExecTypes (otherwise,
+ * there won't be a slot allocated for it).
+ */
+Param *
+generate_new_exec_param(PlannerInfo *root, Oid paramtype, int32 paramtypmod,
+						Oid paramcollation)
+{
+	Param	   *retval;
+
+	retval = makeNode(Param);
+	retval->paramkind = PARAM_EXEC;
+	retval->paramid = list_length(root->glob->paramExecTypes);
+	root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes,
+											 paramtype);
+	retval->paramtype = paramtype;
+	retval->paramtypmod = paramtypmod;
+	retval->paramcollid = paramcollation;
+	retval->location = -1;
+
+	return retval;
+}
+
+/*
+ * Assign a (nonnegative) PARAM_EXEC ID for a special parameter (one that
+ * is not actually used to carry a value at runtime).  Such parameters are
+ * used for special runtime signaling purposes, such as connecting a
+ * recursive union node to its worktable scan node or forcing plan
+ * re-evaluation within the EvalPlanQual mechanism.  No actual Param node
+ * exists with this ID, however.
+ */
+int
+assign_special_exec_param(PlannerInfo *root)
+{
+	int			paramId = list_length(root->glob->paramExecTypes);
+
+	root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes,
+											 InvalidOid);
+	return paramId;
+}
diff --git a/src/include/optimizer/paramassign.h b/src/include/optimizer/paramassign.h
new file mode 100644
index 0000000000000000000000000000000000000000..d18c85c938be08ca4063eb056e2c0060e137e336
--- /dev/null
+++ b/src/include/optimizer/paramassign.h
@@ -0,0 +1,34 @@
+/*-------------------------------------------------------------------------
+ *
+ * paramassign.h
+ *		Functions for assigning PARAM_EXEC slots during planning.
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/optimizer/paramassign.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARAMASSIGN_H
+#define PARAMASSIGN_H
+
+#include "nodes/relation.h"
+
+extern Param *replace_outer_var(PlannerInfo *root, Var *var);
+extern Param *replace_outer_placeholdervar(PlannerInfo *root,
+							 PlaceHolderVar *phv);
+extern Param *replace_outer_agg(PlannerInfo *root, Aggref *agg);
+extern Param *replace_outer_grouping(PlannerInfo *root, GroupingFunc *grp);
+extern Param *replace_nestloop_param_var(PlannerInfo *root, Var *var);
+extern Param *replace_nestloop_param_placeholdervar(PlannerInfo *root,
+									  PlaceHolderVar *phv);
+extern void process_subquery_nestloop_params(PlannerInfo *root,
+								 List *subplan_params);
+extern List *identify_current_nestloop_params(PlannerInfo *root,
+								 Relids leftrelids);
+extern Param *generate_new_exec_param(PlannerInfo *root, Oid paramtype,
+						int32 paramtypmod, Oid paramcollation);
+extern int	assign_special_exec_param(PlannerInfo *root);
+
+#endif							/* PARAMASSIGN_H */
diff --git a/src/include/optimizer/subselect.h b/src/include/optimizer/subselect.h
index d28c993b3a071eb8ce1c94833608148391abfec8..cc4f6e8bdea2e4bcdf19204591fa6c07903172f1 100644
--- a/src/include/optimizer/subselect.h
+++ b/src/include/optimizer/subselect.h
@@ -1,6 +1,7 @@
 /*-------------------------------------------------------------------------
  *
  * subselect.h
+ *	  Planning routines for subselects.
  *
  * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -35,9 +36,8 @@ extern Param *SS_make_initplan_output_param(PlannerInfo *root,
 extern void SS_make_initplan_from_plan(PlannerInfo *root,
 						   PlannerInfo *subroot, Plan *plan,
 						   Param *prm);
-extern Param *assign_nestloop_param_var(PlannerInfo *root, Var *var);
-extern Param *assign_nestloop_param_placeholdervar(PlannerInfo *root,
-									 PlaceHolderVar *phv);
+
+/* XXX deprecated: */
 extern int	SS_assign_special_param(PlannerInfo *root);
 
 #endif							/* SUBSELECT_H */