prm = &(econtext->ecxt_param_exec_vals[paramno]);
                /* Param value should be an OUTER_VAR var */
+               Assert(IsA(nlp->paramval, Var));
                Assert(nlp->paramval->varno == OUTER_VAR);
                Assert(nlp->paramval->varattno > 0);
                prm->value = slot_getattr(outerTupleSlot,
 
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/paths.h"
+#include "optimizer/placeholder.h"
 #include "optimizer/plancat.h"
 #include "optimizer/planmain.h"
 #include "optimizer/predtest.h"
        NestLoopParam *nlp = (NestLoopParam *) lfirst(cell);
 
        next = lnext(cell);
-       if (bms_is_member(nlp->paramval->varno, outerrelids))
+       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);
 
 /*
  * replace_nestloop_params
- *   Replace outer-relation Vars in the given expression with nestloop Params
+ *   Replace outer-relation Vars and PlaceHolderVars in the given expression
+ *   with nestloop Params
  *
- * All Vars belonging to the relation(s) identified by root->curOuterRels
- * are replaced by Params, and entries are added to root->curOuterParams if
- * not already present.
+ * All Vars and PlaceHolderVars belonging to the relation(s) identified by
+ * root->curOuterRels are replaced by Params, and entries are added to
+ * root->curOuterParams if not already present.
  */
 static Node *
 replace_nestloop_params(PlannerInfo *root, Node *expr)
        if (!bms_is_member(var->varno, root->curOuterRels))
            return node;
        /* Create a Param representing the Var */
-       param = assign_nestloop_param(root, var);
+       param = assign_nestloop_param_var(root, var);
        /* Is this param already listed in root->curOuterParams? */
        foreach(lc, root->curOuterParams)
        {
        /* And return the replacement Param */
        return (Node *) param;
    }
+   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);
+
+       /*
+        * If not to be replaced, just return the PlaceHolderVar unmodified.
+        * We use bms_overlap as a cheap/quick test to see if the PHV might
+        * be evaluated in the outer rels, and then grab its PlaceHolderInfo
+        * to tell for sure.
+        */
+       if (!bms_overlap(phv->phrels, root->curOuterRels))
+           return node;
+       if (!bms_is_subset(find_placeholder_info(root, phv, false)->ph_eval_at,
+                          root->curOuterRels))
+           return node;
+       /* 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;
+   }
    return expression_tree_mutator(node,
                                   replace_nestloop_params_mutator,
                                   (void *) root);
  *
  * We have four tasks here:
  * * Remove RestrictInfo nodes from the input clauses.
- * * Replace any outer-relation Var nodes with nestloop Params.
+ * * Replace any outer-relation Var or PHV nodes with nestloop Params.
  *   (XXX eventually, that responsibility should go elsewhere?)
  * * Index keys must be represented by Var nodes with varattno set to the
  *   index's attribute number, not the attribute number in the original rel.
 
                                                   outer_itlist,
                                                   OUTER_VAR,
                                                   rtoffset);
+           /* Check we replaced any PlaceHolderVar with simple Var */
+           if (!(IsA(nlp->paramval, Var) &&
+                 nlp->paramval->varno == OUTER_VAR))
+               elog(ERROR, "NestLoopParam was not reduced to a simple Var");
        }
    }
    else if (IsA(join, MergeJoin))
 
  * the Var to be local to the current query level.
  */
 Param *
-assign_nestloop_param(PlannerInfo *root, Var *var)
+assign_nestloop_param_var(PlannerInfo *root, Var *var)
 {
    Param      *retval;
    int         i;
    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;
+   ListCell   *ppl;
+   PlannerParamItem *pitem;
+   Index       abslevel;
+   int         i;
+
+   Assert(phv->phlevelsup == 0);
+   abslevel = root->query_level;
+
+   /* If there's already a paramlist entry for this same PHV, just use it */
+   /* We assume comparing the PHIDs is sufficient */
+   i = 0;
+   foreach(ppl, root->glob->paramlist)
+   {
+       pitem = (PlannerParamItem *) lfirst(ppl);
+       if (pitem->abslevel == abslevel && IsA(pitem->item, PlaceHolderVar))
+       {
+           PlaceHolderVar *pphv = (PlaceHolderVar *) pitem->item;
+
+           if (pphv->phid == phv->phid)
+               break;
+       }
+       i++;
+   }
+
+   if (ppl == NULL)
+   {
+       /* Nope, so make a new one */
+       phv = (PlaceHolderVar *) copyObject(phv);
+
+       pitem = makeNode(PlannerParamItem);
+       pitem->item = (Node *) phv;
+       pitem->abslevel = abslevel;
+
+       root->glob->paramlist = lappend(root->glob->paramlist, pitem);
+
+       /* i is already the correct list index for the new item */
+   }
+
+   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).
 
  * The nestParams list identifies any executor Params that must be passed
  * into execution of the inner subplan carrying values from the current row
  * of the outer subplan.  Currently we restrict these values to be simple
- * Vars, but perhaps someday that'd be worth relaxing.
+ * Vars, but perhaps someday that'd be worth relaxing.  (Note: during plan
+ * creation, the paramval can actually be a PlaceHolderVar expression; but it
+ * must be a Var with varno OUTER_VAR by the time it gets to the executor.)
  * ----------------
  */
 typedef struct NestLoop
 
  *
  * Each paramlist item shows the absolute query level it is associated with,
  * where the outermost query is level 1 and nested subqueries have higher
- * numbers.  The item the parameter slot represents can be one of three kinds:
+ * numbers.  The item the parameter slot represents can be one of four kinds:
  *
  * A Var: the slot represents a variable of that level that must be passed
  * down because subqueries have outer references to it, or must be passed
  * from a NestLoop node of that level to its inner scan.  The varlevelsup
  * value in the Var will always be zero.
  *
+ * A PlaceHolderVar: this works much like the Var case.  It is currently
+ * only needed for NestLoop parameters, not outer references.
+ *
  * An Aggref (with an expression tree representing its argument): the slot
  * represents an aggregate expression that is an outer reference for some
  * subquery.  The Aggref itself has agglevelsup = 0, and its argument tree
  * for that subplan).  The absolute level shown for such items corresponds
  * to the parent query of the subplan.
  *
- * Note: we detect duplicate Var parameters and coalesce them into one slot,
- * but we do not bother to do this for Aggrefs, and it would be incorrect
- * to do so for Param slots.  Duplicate detection is actually *necessary*
- * in the case of NestLoop parameters since it serves to match up the usage
- * of a Param (in the inner scan) with the assignment of the value (in the
- * NestLoop node). This might result in the same PARAM_EXEC slot being used
- * by multiple NestLoop nodes or SubPlan nodes, but no harm is done since
+ * Note: we detect duplicate Var and PlaceHolderVar parameters and coalesce
+ * them into one slot, but we do not bother to do this for Aggrefs, and it
+ * would be incorrect to do so for Param slots.  Duplicate detection is
+ * actually *necessary* for NestLoop parameters since it serves to match up
+ * the usage of a Param (in the inner scan) with the assignment of the value
+ * (in the NestLoop node). This might result in the same PARAM_EXEC slot being
+ * used by multiple NestLoop nodes or SubPlan nodes, but no harm is done since
  * the same value would be assigned anyway.
  */
 typedef struct PlannerParamItem
 {
    NodeTag     type;
 
-   Node       *item;           /* the Var, Aggref, or Param */
+   Node       *item;           /* the Var, PlaceHolderVar, Aggref, or Param */
    Index       abslevel;       /* its absolute query level */
 } PlannerParamItem;
 
 
                 bool attach_initplans);
 extern Param *SS_make_initplan_from_plan(PlannerInfo *root, Plan *plan,
                    Oid resulttype, int32 resulttypmod, Oid resultcollation);
-extern Param *assign_nestloop_param(PlannerInfo *root, Var *var);
+extern Param *assign_nestloop_param_var(PlannerInfo *root, Var *var);
+extern Param *assign_nestloop_param_placeholdervar(PlannerInfo *root,
+                                                  PlaceHolderVar *phv);
 extern int SS_assign_special_param(PlannerInfo *root);
 
 #endif   /* SUBSELECT_H */
 
 -- regression test: check for bug with propagation of implied equality
 -- to outside an IN
 --
+analyze tenk1;     -- ensure we get consistent plans here
 select count(*) from tenk1 a where unique1 in
   (select unique1 from tenk1 b join tenk1 c using (unique1)
    where b.unique2 = 42);
     1 |    1 |      1 |      1
 (1 row)
 
+--
+-- test case where a PlaceHolderVar is used as a nestloop parameter
+--
+EXPLAIN (COSTS OFF)
+SELECT qq, unique1
+  FROM
+  ( SELECT COALESCE(q1, 0) AS qq FROM int8_tbl a ) AS ss1
+  FULL OUTER JOIN
+  ( SELECT COALESCE(q2, -1) AS qq FROM int8_tbl b ) AS ss2
+  USING (qq)
+  INNER JOIN tenk1 c ON qq = unique2;
+                                              QUERY PLAN                                               
+-------------------------------------------------------------------------------------------------------
+ Nested Loop
+   ->  Hash Full Join
+         Hash Cond: (COALESCE(a.q1, 0::bigint) = COALESCE(b.q2, (-1)::bigint))
+         ->  Seq Scan on int8_tbl a
+         ->  Hash
+               ->  Seq Scan on int8_tbl b
+   ->  Index Scan using tenk1_unique2 on tenk1 c
+         Index Cond: (unique2 = COALESCE((COALESCE(a.q1, 0::bigint)), (COALESCE(b.q2, (-1)::bigint))))
+(8 rows)
+
+SELECT qq, unique1
+  FROM
+  ( SELECT COALESCE(q1, 0) AS qq FROM int8_tbl a ) AS ss1
+  FULL OUTER JOIN
+  ( SELECT COALESCE(q2, -1) AS qq FROM int8_tbl b ) AS ss2
+  USING (qq)
+  INNER JOIN tenk1 c ON qq = unique2;
+ qq  | unique1 
+-----+---------
+ 123 |    4596
+ 123 |    4596
+ 456 |    7318
+(3 rows)
+
 --
 -- test the corner cases FULL JOIN ON TRUE and FULL JOIN ON FALSE
 --
 
 -- regression test: check for bug with propagation of implied equality
 -- to outside an IN
 --
+analyze tenk1;     -- ensure we get consistent plans here
+
 select count(*) from tenk1 a where unique1 in
   (select unique1 from tenk1 b join tenk1 c using (unique1)
    where b.unique2 = 42);
 ) sub2
 ON sub1.key1 = sub2.key3;
 
+--
+-- test case where a PlaceHolderVar is used as a nestloop parameter
+--
+
+EXPLAIN (COSTS OFF)
+SELECT qq, unique1
+  FROM
+  ( SELECT COALESCE(q1, 0) AS qq FROM int8_tbl a ) AS ss1
+  FULL OUTER JOIN
+  ( SELECT COALESCE(q2, -1) AS qq FROM int8_tbl b ) AS ss2
+  USING (qq)
+  INNER JOIN tenk1 c ON qq = unique2;
+
+SELECT qq, unique1
+  FROM
+  ( SELECT COALESCE(q1, 0) AS qq FROM int8_tbl a ) AS ss1
+  FULL OUTER JOIN
+  ( SELECT COALESCE(q2, -1) AS qq FROM int8_tbl b ) AS ss2
+  USING (qq)
+  INNER JOIN tenk1 c ON qq = unique2;
+
 --
 -- test the corner cases FULL JOIN ON TRUE and FULL JOIN ON FALSE
 --