commit 2d9bef962ac66d24377b456b6d381a05f27168fb
Author: Tom Lane <tgl@sss.pgh.pa.us>
Date:   Thu Dec 22 16:19:42 2022 -0500

    Do assorted preliminary refactoring.
    
    This patch doesn't actually change any behavior, just lay some
    fairly boring groundwork:
    
    * Add Var.varnullingrels and PlaceHolderVar.phnullingrels fields.
    These fields are always empty as of this commit, so they don't
    affect any behavior, even though equal() will compare them.
    Update backend/nodes/ and backend/rewrite/ infrastructure as needed.
    
    * Refactor deconstruct_jointree() to split it into two passes;
    the first pass computes relid bitmapsets and the second performs
    qual distribution.  The reason for this is that when we invent
    "join domains", several patches later, we'll need to know the
    full contents of the domains before distributing quals.  It
    seems like a good idea to split up the function anyway, as it's
    gotten quite large.
    
    * Move calculation of all_baserels to deconstruct_jointree,
    because we'll soon need it to be available earlier.
    (This means that remove_rel_from_query now has to update it.)
    
    * Refactor to pass the relevant SpecialJoinInfo to
    reconsider_outer_join_clause and reconsider_full_join_clause.
    We'll need that later.
    
    * Add some rewrite functions we'll need later for adding and
    removing nullingrel bits in expression trees.
    
    Note this will require a catversion bump when committed.

diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index c85d8fe975..cced668f58 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -80,11 +80,13 @@ makeVar(int varno,
 	var->varlevelsup = varlevelsup;
 
 	/*
-	 * Only a few callers need to make Var nodes with varnosyn/varattnosyn
-	 * different from varno/varattno.  We don't provide separate arguments for
-	 * them, but just initialize them to the given varno/varattno.  This
-	 * reduces code clutter and chance of error for most callers.
+	 * Only a few callers need to make Var nodes with non-null varnullingrels,
+	 * or with varnosyn/varattnosyn different from varno/varattno.  We don't
+	 * provide separate arguments for them, but just initialize them to NULL
+	 * and the given varno/varattno.  This reduces code clutter and chance of
+	 * error for most callers.
 	 */
+	var->varnullingrels = NULL;
 	var->varnosyn = (Index) varno;
 	var->varattnosyn = varattno;
 
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index af8620ceb7..62245ae945 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2641,6 +2641,7 @@ expression_tree_mutator_impl(Node *node,
 				Var		   *newnode;
 
 				FLATCOPY(newnode, var, Var);
+				/* Assume we need not copy the varnullingrels bitmapset */
 				return (Node *) newnode;
 			}
 			break;
@@ -3234,7 +3235,7 @@ expression_tree_mutator_impl(Node *node,
 
 				FLATCOPY(newnode, phv, PlaceHolderVar);
 				MUTATE(newnode->phexpr, phv->phexpr, Expr *);
-				/* Assume we need not copy the relids bitmapset */
+				/* Assume we need not copy the relids bitmapsets */
 				return (Node *) newnode;
 			}
 			break;
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 0cfa3a1d6c..10ac01ac36 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -159,27 +159,6 @@ make_one_rel(PlannerInfo *root, List *joinlist)
 	Index		rti;
 	double		total_pages;
 
-	/*
-	 * Construct the all_baserels Relids set.
-	 */
-	root->all_baserels = NULL;
-	for (rti = 1; rti < root->simple_rel_array_size; rti++)
-	{
-		RelOptInfo *brel = root->simple_rel_array[rti];
-
-		/* there may be empty slots corresponding to non-baserel RTEs */
-		if (brel == NULL)
-			continue;
-
-		Assert(brel->relid == rti); /* sanity check on array */
-
-		/* ignore RTEs that are "other rels" */
-		if (brel->reloptkind != RELOPT_BASEREL)
-			continue;
-
-		root->all_baserels = bms_add_member(root->all_baserels, brel->relid);
-	}
-
 	/* Mark base rels as to whether we care about fast-start plans */
 	set_base_rel_consider_startup(root);
 
@@ -207,6 +186,7 @@ make_one_rel(PlannerInfo *root, List *joinlist)
 	{
 		RelOptInfo *brel = root->simple_rel_array[rti];
 
+		/* there may be empty slots corresponding to non-baserel RTEs */
 		if (brel == NULL)
 			continue;
 
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index e65b967b1f..019972cefc 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -61,10 +61,10 @@ static RestrictInfo *create_join_clause(PlannerInfo *root,
 										EquivalenceMember *rightem,
 										EquivalenceClass *parent_ec);
 static bool reconsider_outer_join_clause(PlannerInfo *root,
-										 RestrictInfo *rinfo,
+										 OuterJoinClauseInfo *ojcinfo,
 										 bool outer_on_left);
 static bool reconsider_full_join_clause(PlannerInfo *root,
-										RestrictInfo *rinfo);
+										OuterJoinClauseInfo *ojcinfo);
 static Bitmapset *get_eclass_indexes_for_relids(PlannerInfo *root,
 												Relids relids);
 static Bitmapset *get_common_eclass_indexes(PlannerInfo *root, Relids relids1,
@@ -1977,10 +1977,12 @@ reconsider_outer_join_clauses(PlannerInfo *root)
 		/* Process the LEFT JOIN clauses */
 		foreach(cell, root->left_join_clauses)
 		{
-			RestrictInfo *rinfo = (RestrictInfo *) lfirst(cell);
+			OuterJoinClauseInfo *ojcinfo = (OuterJoinClauseInfo *) lfirst(cell);
 
-			if (reconsider_outer_join_clause(root, rinfo, true))
+			if (reconsider_outer_join_clause(root, ojcinfo, true))
 			{
+				RestrictInfo *rinfo = ojcinfo->rinfo;
+
 				found = true;
 				/* remove it from the list */
 				root->left_join_clauses =
@@ -1996,10 +1998,12 @@ reconsider_outer_join_clauses(PlannerInfo *root)
 		/* Process the RIGHT JOIN clauses */
 		foreach(cell, root->right_join_clauses)
 		{
-			RestrictInfo *rinfo = (RestrictInfo *) lfirst(cell);
+			OuterJoinClauseInfo *ojcinfo = (OuterJoinClauseInfo *) lfirst(cell);
 
-			if (reconsider_outer_join_clause(root, rinfo, false))
+			if (reconsider_outer_join_clause(root, ojcinfo, false))
 			{
+				RestrictInfo *rinfo = ojcinfo->rinfo;
+
 				found = true;
 				/* remove it from the list */
 				root->right_join_clauses =
@@ -2015,10 +2019,12 @@ reconsider_outer_join_clauses(PlannerInfo *root)
 		/* Process the FULL JOIN clauses */
 		foreach(cell, root->full_join_clauses)
 		{
-			RestrictInfo *rinfo = (RestrictInfo *) lfirst(cell);
+			OuterJoinClauseInfo *ojcinfo = (OuterJoinClauseInfo *) lfirst(cell);
 
-			if (reconsider_full_join_clause(root, rinfo))
+			if (reconsider_full_join_clause(root, ojcinfo))
 			{
+				RestrictInfo *rinfo = ojcinfo->rinfo;
+
 				found = true;
 				/* remove it from the list */
 				root->full_join_clauses =
@@ -2035,21 +2041,21 @@ reconsider_outer_join_clauses(PlannerInfo *root)
 	/* Now, any remaining clauses have to be thrown back */
 	foreach(cell, root->left_join_clauses)
 	{
-		RestrictInfo *rinfo = (RestrictInfo *) lfirst(cell);
+		OuterJoinClauseInfo *ojcinfo = (OuterJoinClauseInfo *) lfirst(cell);
 
-		distribute_restrictinfo_to_rels(root, rinfo);
+		distribute_restrictinfo_to_rels(root, ojcinfo->rinfo);
 	}
 	foreach(cell, root->right_join_clauses)
 	{
-		RestrictInfo *rinfo = (RestrictInfo *) lfirst(cell);
+		OuterJoinClauseInfo *ojcinfo = (OuterJoinClauseInfo *) lfirst(cell);
 
-		distribute_restrictinfo_to_rels(root, rinfo);
+		distribute_restrictinfo_to_rels(root, ojcinfo->rinfo);
 	}
 	foreach(cell, root->full_join_clauses)
 	{
-		RestrictInfo *rinfo = (RestrictInfo *) lfirst(cell);
+		OuterJoinClauseInfo *ojcinfo = (OuterJoinClauseInfo *) lfirst(cell);
 
-		distribute_restrictinfo_to_rels(root, rinfo);
+		distribute_restrictinfo_to_rels(root, ojcinfo->rinfo);
 	}
 }
 
@@ -2059,9 +2065,10 @@ reconsider_outer_join_clauses(PlannerInfo *root)
  * Returns true if we were able to propagate a constant through the clause.
  */
 static bool
-reconsider_outer_join_clause(PlannerInfo *root, RestrictInfo *rinfo,
+reconsider_outer_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo,
 							 bool outer_on_left)
 {
+	RestrictInfo *rinfo = ojcinfo->rinfo;
 	Expr	   *outervar,
 			   *innervar;
 	Oid			opno,
@@ -2185,8 +2192,9 @@ reconsider_outer_join_clause(PlannerInfo *root, RestrictInfo *rinfo,
  * Returns true if we were able to propagate a constant through the clause.
  */
 static bool
-reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo)
+reconsider_full_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo)
 {
+	RestrictInfo *rinfo = ojcinfo->rinfo;
 	Expr	   *leftvar;
 	Expr	   *rightvar;
 	Oid			opno,
diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index bbeca9a9ab..dc0f165477 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -349,6 +349,11 @@ remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids)
 		}
 	}
 
+	/*
+	 * Update all_baserels and related relid sets.
+	 */
+	root->all_baserels = bms_del_member(root->all_baserels, relid);
+
 	/*
 	 * Likewise remove references from SpecialJoinInfo data structures.
 	 *
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index fd8cbb1dc7..2ee58f0a68 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -40,7 +40,40 @@ int			from_collapse_limit;
 int			join_collapse_limit;
 
 
-/* Elements of the postponed_qual_list used during deconstruct_recurse */
+/*
+ * deconstruct_jointree requires multiple passes over the join tree, because we
+ * need to finish computing JoinDomains before we start distributing quals.
+ * As long as we have to do that, other information such as the relevant
+ * qualscopes might as well be computed in the first pass too.
+ *
+ * deconstruct_recurse recursively examines the join tree and builds a List
+ * (in depth-first traversal order) of JoinTreeItem structs, which are then
+ * processed iteratively by deconstruct_distribute.
+ *
+ * The JoinTreeItem structs themselves can be freed at the end of
+ * deconstruct_jointree, but do not modify or free their substructure,
+ * as the relid sets may also be pointed to by RestrictInfo and
+ * SpecialJoinInfo nodes.
+ */
+typedef struct JoinTreeItem
+{
+	/* Fields filled during deconstruct_recurse: */
+	Node	   *jtnode;			/* jointree node to examine */
+	bool		below_outer_join;	/* is it below an outer join? */
+	Relids		qualscope;		/* base Relids syntactically included in this
+								 * jointree node */
+	Relids		inner_join_rels;	/* base Relids syntactically included in
+									 * inner joins appearing at or below this
+									 * jointree node */
+	Relids		left_rels;		/* if join node, Relids of the left side */
+	Relids		right_rels;		/* if join node, Relids of the right side */
+	Relids		nonnullable_rels;	/* if outer join, Relids of the
+									 * non-nullable side */
+	/* Fields filled during deconstruct_distribute: */
+	SpecialJoinInfo *sjinfo;	/* if outer join, its SpecialJoinInfo */
+} JoinTreeItem;
+
+/* Elements of the postponed_qual_list used during deconstruct_distribute */
 typedef struct PostponedQual
 {
 	Node	   *qual;			/* a qual clause waiting to be processed */
@@ -52,8 +85,9 @@ static void extract_lateral_references(PlannerInfo *root, RelOptInfo *brel,
 									   Index rtindex);
 static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
 								 bool below_outer_join,
-								 Relids *qualscope, Relids *inner_join_rels,
-								 List **postponed_qual_list);
+								 List **item_list);
+static void deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem,
+								   List **postponed_qual_list);
 static void process_security_barrier_quals(PlannerInfo *root,
 										   int rti, Relids qualscope,
 										   bool below_outer_join);
@@ -63,9 +97,17 @@ static SpecialJoinInfo *make_outerjoininfo(PlannerInfo *root,
 										   JoinType jointype, List *clause);
 static void compute_semijoin_info(PlannerInfo *root, SpecialJoinInfo *sjinfo,
 								  List *clause);
+static void distribute_quals_to_rels(PlannerInfo *root, List *clauses,
+									 bool below_outer_join,
+									 SpecialJoinInfo *sjinfo,
+									 Index security_level,
+									 Relids qualscope,
+									 Relids ojscope,
+									 Relids outerjoin_nonnullable,
+									 List **postponed_qual_list);
 static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 									bool below_outer_join,
-									JoinType jointype,
+									SpecialJoinInfo *sjinfo,
 									Index security_level,
 									Relids qualscope,
 									Relids ojscope,
@@ -683,9 +725,9 @@ List *
 deconstruct_jointree(PlannerInfo *root)
 {
 	List	   *result;
-	Relids		qualscope;
-	Relids		inner_join_rels;
+	List	   *item_list = NIL;
 	List	   *postponed_qual_list = NIL;
+	ListCell   *lc;
 
 	/*
 	 * After this point, no more PlaceHolderInfos may be made, because
@@ -699,98 +741,105 @@ deconstruct_jointree(PlannerInfo *root)
 	Assert(root->parse->jointree != NULL &&
 		   IsA(root->parse->jointree, FromExpr));
 
-	/* this is filled as we scan the jointree */
+	/* These are filled as we scan the jointree */
+	root->all_baserels = NULL;
 	root->nullable_baserels = NULL;
 
-	result = deconstruct_recurse(root, (Node *) root->parse->jointree, false,
-								 &qualscope, &inner_join_rels,
-								 &postponed_qual_list);
+	/* Perform the initial scan of the jointree */
+	result = deconstruct_recurse(root, (Node *) root->parse->jointree,
+								 false,
+								 &item_list);
 
-	/* Shouldn't be any leftover quals */
+	/* Now scan all the jointree nodes again, and distribute quals */
+	foreach(lc, item_list)
+	{
+		JoinTreeItem *jtitem = (JoinTreeItem *) lfirst(lc);
+
+		deconstruct_distribute(root, jtitem,
+							   &postponed_qual_list);
+	}
+
+	/* Shouldn't be any leftover postponed quals */
 	Assert(postponed_qual_list == NIL);
 
+	/* Don't need the JoinTreeItems any more */
+	list_free_deep(item_list);
+
 	return result;
 }
 
 /*
  * deconstruct_recurse
- *	  One recursion level of deconstruct_jointree processing.
+ *	  One recursion level of deconstruct_jointree's initial jointree scan.
  *
  * Inputs:
  *	jtnode is the jointree node to examine
  *	below_outer_join is true if this node is within the nullable side of a
  *		higher-level outer join
- * Outputs:
- *	*qualscope gets the set of base Relids syntactically included in this
- *		jointree node (do not modify or free this, as it may also be pointed
- *		to by RestrictInfo and SpecialJoinInfo nodes)
- *	*inner_join_rels gets the set of base Relids syntactically included in
- *		inner joins appearing at or below this jointree node (do not modify
- *		or free this, either)
- *	*postponed_qual_list is a list of PostponedQual structs, which we can
- *		add quals to if they turn out to belong to a higher join level
- *	Return value is the appropriate joinlist for this jointree node
  *
- * In addition, entries will be added to root->join_info_list for outer joins.
+ * item_list is an in/out parameter: we add a JoinTreeItem struct to
+ * that list for each jointree node, in depth-first traversal order.
+ * (Hence, after each call, the last list item corresponds to its jtnode.)
+ *
+ * Return value is the appropriate joinlist for this jointree node.
  */
 static List *
-deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
-					Relids *qualscope, Relids *inner_join_rels,
-					List **postponed_qual_list)
+deconstruct_recurse(PlannerInfo *root, Node *jtnode,
+					bool below_outer_join,
+					List **item_list)
 {
 	List	   *joinlist;
+	JoinTreeItem *jtitem;
+
+	Assert(jtnode != NULL);
+
+	/* Make the new JoinTreeItem, but don't add it to item_list yet */
+	jtitem = palloc0_object(JoinTreeItem);
+	jtitem->jtnode = jtnode;
+	jtitem->below_outer_join = below_outer_join;
 
-	if (jtnode == NULL)
-	{
-		*qualscope = NULL;
-		*inner_join_rels = NULL;
-		return NIL;
-	}
 	if (IsA(jtnode, RangeTblRef))
 	{
 		int			varno = ((RangeTblRef *) jtnode)->rtindex;
 
+		/* Fill all_baserels as we encounter baserel jointree nodes */
+		root->all_baserels = bms_add_member(root->all_baserels, varno);
 		/* qualscope is just the one RTE */
-		*qualscope = bms_make_singleton(varno);
-		/* Deal with any securityQuals attached to the RTE */
-		if (root->qual_security_level > 0)
-			process_security_barrier_quals(root,
-										   varno,
-										   *qualscope,
-										   below_outer_join);
+		jtitem->qualscope = bms_make_singleton(varno);
 		/* A single baserel does not create an inner join */
-		*inner_join_rels = NULL;
+		jtitem->inner_join_rels = NULL;
 		joinlist = list_make1(jtnode);
 	}
 	else if (IsA(jtnode, FromExpr))
 	{
 		FromExpr   *f = (FromExpr *) jtnode;
-		List	   *child_postponed_quals = NIL;
 		int			remaining;
 		ListCell   *l;
 
 		/*
-		 * First, recurse to handle child joins.  We collapse subproblems into
-		 * a single joinlist whenever the resulting joinlist wouldn't exceed
-		 * from_collapse_limit members.  Also, always collapse one-element
-		 * subproblems, since that won't lengthen the joinlist anyway.
+		 * Recurse to handle child nodes, and compute output joinlist.  We
+		 * collapse subproblems into a single joinlist whenever the resulting
+		 * joinlist wouldn't exceed from_collapse_limit members.  Also, always
+		 * collapse one-element subproblems, since that won't lengthen the
+		 * joinlist anyway.
 		 */
-		*qualscope = NULL;
-		*inner_join_rels = NULL;
+		jtitem->qualscope = NULL;
+		jtitem->inner_join_rels = NULL;
 		joinlist = NIL;
 		remaining = list_length(f->fromlist);
 		foreach(l, f->fromlist)
 		{
-			Relids		sub_qualscope;
+			JoinTreeItem *sub_item;
 			List	   *sub_joinlist;
 			int			sub_members;
 
 			sub_joinlist = deconstruct_recurse(root, lfirst(l),
 											   below_outer_join,
-											   &sub_qualscope,
-											   inner_join_rels,
-											   &child_postponed_quals);
-			*qualscope = bms_add_members(*qualscope, sub_qualscope);
+											   item_list);
+			sub_item = (JoinTreeItem *) llast(*item_list);
+			jtitem->qualscope = bms_add_members(jtitem->qualscope,
+												sub_item->qualscope);
+			jtitem->inner_join_rels = sub_item->inner_join_rels;
 			sub_members = list_length(sub_joinlist);
 			remaining--;
 			if (sub_members <= 1 ||
@@ -808,115 +857,80 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 		 * that still possible?) the initialization before the loop fixed it.
 		 */
 		if (list_length(f->fromlist) > 1)
-			*inner_join_rels = *qualscope;
-
-		/*
-		 * Try to process any quals postponed by children.  If they need
-		 * further postponement, add them to my output postponed_qual_list.
-		 */
-		foreach(l, child_postponed_quals)
-		{
-			PostponedQual *pq = (PostponedQual *) lfirst(l);
-
-			if (bms_is_subset(pq->relids, *qualscope))
-				distribute_qual_to_rels(root, pq->qual,
-										below_outer_join, JOIN_INNER,
-										root->qual_security_level,
-										*qualscope, NULL, NULL,
-										NULL);
-			else
-				*postponed_qual_list = lappend(*postponed_qual_list, pq);
-		}
-
-		/*
-		 * Now process the top-level quals.
-		 */
-		foreach(l, (List *) f->quals)
-		{
-			Node	   *qual = (Node *) lfirst(l);
-
-			distribute_qual_to_rels(root, qual,
-									below_outer_join, JOIN_INNER,
-									root->qual_security_level,
-									*qualscope, NULL, NULL,
-									postponed_qual_list);
-		}
+			jtitem->inner_join_rels = jtitem->qualscope;
 	}
 	else if (IsA(jtnode, JoinExpr))
 	{
 		JoinExpr   *j = (JoinExpr *) jtnode;
-		List	   *child_postponed_quals = NIL;
-		Relids		leftids,
-					rightids,
-					left_inners,
-					right_inners,
-					nonnullable_rels,
-					nullable_rels,
-					ojscope;
+		Relids		nullable_rels;
+		JoinTreeItem *left_item,
+				   *right_item;
 		List	   *leftjoinlist,
 				   *rightjoinlist;
-		List	   *my_quals;
-		SpecialJoinInfo *sjinfo;
-		ListCell   *l;
 
-		/*
-		 * Order of operations here is subtle and critical.  First we recurse
-		 * to handle sub-JOINs.  Their join quals will be placed without
-		 * regard for whether this level is an outer join, which is correct.
-		 * Then we place our own join quals, which are restricted by lower
-		 * outer joins in any case, and are forced to this level if this is an
-		 * outer join and they mention the outer side.  Finally, if this is an
-		 * outer join, we create a join_info_list entry for the join.  This
-		 * will prevent quals above us in the join tree that use those rels
-		 * from being pushed down below this level.  (It's okay for upper
-		 * quals to be pushed down to the outer side, however.)
-		 */
 		switch (j->jointype)
 		{
 			case JOIN_INNER:
+				/* Recurse */
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   below_outer_join,
-												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   item_list);
+				left_item = (JoinTreeItem *) llast(*item_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													below_outer_join,
-													&rightids, &right_inners,
-													&child_postponed_quals);
-				*qualscope = bms_union(leftids, rightids);
-				*inner_join_rels = *qualscope;
+													item_list);
+				right_item = (JoinTreeItem *) llast(*item_list);
+				/* Compute qualscope etc */
+				jtitem->qualscope = bms_union(left_item->qualscope,
+											  right_item->qualscope);
+				jtitem->inner_join_rels = jtitem->qualscope;
+				jtitem->left_rels = left_item->qualscope;
+				jtitem->right_rels = right_item->qualscope;
 				/* Inner join adds no restrictions for quals */
-				nonnullable_rels = NULL;
+				jtitem->nonnullable_rels = NULL;
 				/* and it doesn't force anything to null, either */
 				nullable_rels = NULL;
 				break;
 			case JOIN_LEFT:
 			case JOIN_ANTI:
+				/* Recurse */
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   below_outer_join,
-												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   item_list);
+				left_item = (JoinTreeItem *) llast(*item_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													true,
-													&rightids, &right_inners,
-													&child_postponed_quals);
-				*qualscope = bms_union(leftids, rightids);
-				*inner_join_rels = bms_union(left_inners, right_inners);
-				nonnullable_rels = leftids;
-				nullable_rels = rightids;
+													item_list);
+				right_item = (JoinTreeItem *) llast(*item_list);
+				/* Compute qualscope etc */
+				jtitem->qualscope = bms_union(left_item->qualscope,
+											  right_item->qualscope);
+				jtitem->inner_join_rels = bms_union(left_item->inner_join_rels,
+													right_item->inner_join_rels);
+				jtitem->left_rels = left_item->qualscope;
+				jtitem->right_rels = right_item->qualscope;
+				jtitem->nonnullable_rels = left_item->qualscope;
+				nullable_rels = right_item->qualscope;
 				break;
 			case JOIN_SEMI:
+				/* Recurse */
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   below_outer_join,
-												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   item_list);
+				left_item = (JoinTreeItem *) llast(*item_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													below_outer_join,
-													&rightids, &right_inners,
-													&child_postponed_quals);
-				*qualscope = bms_union(leftids, rightids);
-				*inner_join_rels = bms_union(left_inners, right_inners);
+													item_list);
+				right_item = (JoinTreeItem *) llast(*item_list);
+				/* Compute qualscope etc */
+				jtitem->qualscope = bms_union(left_item->qualscope,
+											  right_item->qualscope);
+				jtitem->inner_join_rels = bms_union(left_item->inner_join_rels,
+													right_item->inner_join_rels);
+				jtitem->left_rels = left_item->qualscope;
+				jtitem->right_rels = right_item->qualscope;
 				/* Semi join adds no restrictions for quals */
-				nonnullable_rels = NULL;
+				jtitem->nonnullable_rels = NULL;
 
 				/*
 				 * Theoretically, a semijoin would null the RHS; but since the
@@ -926,27 +940,32 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 				nullable_rels = NULL;
 				break;
 			case JOIN_FULL:
+				/* Recurse */
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   true,
-												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   item_list);
+				left_item = (JoinTreeItem *) llast(*item_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													true,
-													&rightids, &right_inners,
-													&child_postponed_quals);
-				*qualscope = bms_union(leftids, rightids);
-				*inner_join_rels = bms_union(left_inners, right_inners);
+													item_list);
+				right_item = (JoinTreeItem *) llast(*item_list);
+				/* Compute qualscope etc */
+				jtitem->qualscope = bms_union(left_item->qualscope,
+											  right_item->qualscope);
+				jtitem->inner_join_rels = bms_union(left_item->inner_join_rels,
+													right_item->inner_join_rels);
+				jtitem->left_rels = left_item->qualscope;
+				jtitem->right_rels = right_item->qualscope;
 				/* each side is both outer and inner */
-				nonnullable_rels = *qualscope;
-				nullable_rels = *qualscope;
+				jtitem->nonnullable_rels = jtitem->qualscope;
+				nullable_rels = jtitem->qualscope;
 				break;
 			default:
 				/* JOIN_RIGHT was eliminated during reduce_outer_joins() */
 				elog(ERROR, "unrecognized join type: %d",
 					 (int) j->jointype);
-				nonnullable_rels = NULL;	/* keep compiler quiet */
+				leftjoinlist = rightjoinlist = NIL; /* keep compiler quiet */
 				nullable_rels = NULL;
-				leftjoinlist = rightjoinlist = NIL;
 				break;
 		}
 
@@ -954,6 +973,131 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 		root->nullable_baserels = bms_add_members(root->nullable_baserels,
 												  nullable_rels);
 
+		/*
+		 * Compute the output joinlist.  We fold subproblems together except
+		 * at a FULL JOIN or where join_collapse_limit would be exceeded.
+		 */
+		if (j->jointype == JOIN_FULL)
+		{
+			/* force the join order exactly at this node */
+			joinlist = list_make1(list_make2(leftjoinlist, rightjoinlist));
+		}
+		else if (list_length(leftjoinlist) + list_length(rightjoinlist) <=
+				 join_collapse_limit)
+		{
+			/* OK to combine subproblems */
+			joinlist = list_concat(leftjoinlist, rightjoinlist);
+		}
+		else
+		{
+			/* can't combine, but needn't force join order above here */
+			Node	   *leftpart,
+					   *rightpart;
+
+			/* avoid creating useless 1-element sublists */
+			if (list_length(leftjoinlist) == 1)
+				leftpart = (Node *) linitial(leftjoinlist);
+			else
+				leftpart = (Node *) leftjoinlist;
+			if (list_length(rightjoinlist) == 1)
+				rightpart = (Node *) linitial(rightjoinlist);
+			else
+				rightpart = (Node *) rightjoinlist;
+			joinlist = list_make2(leftpart, rightpart);
+		}
+	}
+	else
+	{
+		elog(ERROR, "unrecognized node type: %d",
+			 (int) nodeTag(jtnode));
+		joinlist = NIL;			/* keep compiler quiet */
+	}
+
+	/* Finally, we can add the new JoinTreeItem to item_list */
+	*item_list = lappend(*item_list, jtitem);
+
+	return joinlist;
+}
+
+/*
+ * deconstruct_distribute
+ *	  Process one jointree node in phase 2 of deconstruct_jointree processing.
+ *
+ * Distribute quals of the node to appropriate restriction and join lists.
+ * In addition, entries will be added to root->join_info_list for outer joins.
+ *
+ * Inputs:
+ *	jtitem is the JoinTreeItem to examine
+ * Input/Outputs:
+ *	*postponed_qual_list is a list of PostponedQual structs
+ *
+ * On entry, *postponed_qual_list contains any quals that had to be postponed
+ * out of lower join levels (because they contain lateral references).
+ * On exit, *postponed_qual_list contains quals that can't be processed yet
+ * (because their lateral references are still unsatisfied).
+ */
+static void
+deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem,
+					   List **postponed_qual_list)
+{
+	Node	   *jtnode = jtitem->jtnode;
+
+	if (IsA(jtnode, RangeTblRef))
+	{
+		int			varno = ((RangeTblRef *) jtnode)->rtindex;
+
+		/* Deal with any securityQuals attached to the RTE */
+		if (root->qual_security_level > 0)
+			process_security_barrier_quals(root,
+										   varno,
+										   jtitem->qualscope,
+										   jtitem->below_outer_join);
+	}
+	else if (IsA(jtnode, FromExpr))
+	{
+		FromExpr   *f = (FromExpr *) jtnode;
+		List	   *new_postponed_quals = NIL;
+		ListCell   *l;
+
+		/*
+		 * Try to process any quals postponed by children.  If they need
+		 * further postponement, add them to my output postponed_qual_list.
+		 */
+		foreach(l, *postponed_qual_list)
+		{
+			PostponedQual *pq = (PostponedQual *) lfirst(l);
+
+			if (bms_is_subset(pq->relids, jtitem->qualscope))
+				distribute_qual_to_rels(root, pq->qual,
+										jtitem->below_outer_join,
+										NULL,
+										root->qual_security_level,
+										jtitem->qualscope, NULL, NULL,
+										NULL);
+			else
+				new_postponed_quals = lappend(new_postponed_quals, pq);
+		}
+		*postponed_qual_list = new_postponed_quals;
+
+		/*
+		 * Now process the top-level quals.
+		 */
+		distribute_quals_to_rels(root, (List *) f->quals,
+								 jtitem->below_outer_join,
+								 NULL,
+								 root->qual_security_level,
+								 jtitem->qualscope, NULL, NULL,
+								 postponed_qual_list);
+	}
+	else if (IsA(jtnode, JoinExpr))
+	{
+		JoinExpr   *j = (JoinExpr *) jtnode;
+		List	   *new_postponed_quals = NIL;
+		Relids		ojscope;
+		List	   *my_quals;
+		SpecialJoinInfo *sjinfo;
+		ListCell   *l;
+
 		/*
 		 * Try to process any quals postponed by children.  If they need
 		 * further postponement, add them to my output postponed_qual_list.
@@ -961,11 +1105,11 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 		 * that they'll be handled properly in make_outerjoininfo.
 		 */
 		my_quals = NIL;
-		foreach(l, child_postponed_quals)
+		foreach(l, *postponed_qual_list)
 		{
 			PostponedQual *pq = (PostponedQual *) lfirst(l);
 
-			if (bms_is_subset(pq->relids, *qualscope))
+			if (bms_is_subset(pq->relids, jtitem->qualscope))
 				my_quals = lappend(my_quals, pq->qual);
 			else
 			{
@@ -974,16 +1118,15 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 				 * If this Assert fires, pull_up_subqueries() messed up.
 				 */
 				Assert(j->jointype == JOIN_INNER);
-				*postponed_qual_list = lappend(*postponed_qual_list, pq);
+				new_postponed_quals = lappend(new_postponed_quals, pq);
 			}
 		}
+		*postponed_qual_list = new_postponed_quals;
 		my_quals = list_concat(my_quals, (List *) j->quals);
 
 		/*
-		 * For an OJ, form the SpecialJoinInfo now, because we need the OJ's
-		 * semantic scope (ojscope) to pass to distribute_qual_to_rels.  But
-		 * we mustn't add it to join_info_list just yet, because we don't want
-		 * distribute_qual_to_rels to think it is an outer join below us.
+		 * For an OJ, form the SpecialJoinInfo now, so that we can pass it to
+		 * distribute_qual_to_rels.  We must compute its ojscope too.
 		 *
 		 * Semijoins are a bit of a hybrid: we build a SpecialJoinInfo, but we
 		 * want ojscope = NULL for distribute_qual_to_rels.
@@ -991,15 +1134,19 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 		if (j->jointype != JOIN_INNER)
 		{
 			sjinfo = make_outerjoininfo(root,
-										leftids, rightids,
-										*inner_join_rels,
+										jtitem->left_rels,
+										jtitem->right_rels,
+										jtitem->inner_join_rels,
 										j->jointype,
 										my_quals);
+			jtitem->sjinfo = sjinfo;
 			if (j->jointype == JOIN_SEMI)
 				ojscope = NULL;
 			else
+			{
 				ojscope = bms_union(sjinfo->min_lefthand,
 									sjinfo->min_righthand);
+			}
 		}
 		else
 		{
@@ -1008,67 +1155,27 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 		}
 
 		/* Process the JOIN's qual clauses */
-		foreach(l, my_quals)
-		{
-			Node	   *qual = (Node *) lfirst(l);
-
-			distribute_qual_to_rels(root, qual,
-									below_outer_join, j->jointype,
-									root->qual_security_level,
-									*qualscope,
-									ojscope, nonnullable_rels,
-									postponed_qual_list);
-		}
-
-		/* Now we can add the SpecialJoinInfo to join_info_list */
+		distribute_quals_to_rels(root, my_quals,
+								 jtitem->below_outer_join,
+								 sjinfo,
+								 root->qual_security_level,
+								 jtitem->qualscope,
+								 ojscope, jtitem->nonnullable_rels,
+								 postponed_qual_list);
+
+		/* And add the SpecialJoinInfo to join_info_list */
 		if (sjinfo)
 		{
 			root->join_info_list = lappend(root->join_info_list, sjinfo);
 			/* Each time we do that, recheck placeholder eval levels */
 			update_placeholder_eval_levels(root, sjinfo);
 		}
-
-		/*
-		 * Finally, compute the output joinlist.  We fold subproblems together
-		 * except at a FULL JOIN or where join_collapse_limit would be
-		 * exceeded.
-		 */
-		if (j->jointype == JOIN_FULL)
-		{
-			/* force the join order exactly at this node */
-			joinlist = list_make1(list_make2(leftjoinlist, rightjoinlist));
-		}
-		else if (list_length(leftjoinlist) + list_length(rightjoinlist) <=
-				 join_collapse_limit)
-		{
-			/* OK to combine subproblems */
-			joinlist = list_concat(leftjoinlist, rightjoinlist);
-		}
-		else
-		{
-			/* can't combine, but needn't force join order above here */
-			Node	   *leftpart,
-					   *rightpart;
-
-			/* avoid creating useless 1-element sublists */
-			if (list_length(leftjoinlist) == 1)
-				leftpart = (Node *) linitial(leftjoinlist);
-			else
-				leftpart = (Node *) leftjoinlist;
-			if (list_length(rightjoinlist) == 1)
-				rightpart = (Node *) linitial(rightjoinlist);
-			else
-				rightpart = (Node *) rightjoinlist;
-			joinlist = list_make2(leftpart, rightpart);
-		}
 	}
 	else
 	{
 		elog(ERROR, "unrecognized node type: %d",
 			 (int) nodeTag(jtnode));
-		joinlist = NIL;			/* keep compiler quiet */
 	}
-	return joinlist;
 }
 
 /*
@@ -1102,27 +1209,21 @@ process_security_barrier_quals(PlannerInfo *root,
 	foreach(lc, rte->securityQuals)
 	{
 		List	   *qualset = (List *) lfirst(lc);
-		ListCell   *lc2;
-
-		foreach(lc2, qualset)
-		{
-			Node	   *qual = (Node *) lfirst(lc2);
 
-			/*
-			 * We cheat to the extent of passing ojscope = qualscope rather
-			 * than its more logical value of NULL.  The only effect this has
-			 * is to force a Var-free qual to be evaluated at the rel rather
-			 * than being pushed up to top of tree, which we don't want.
-			 */
-			distribute_qual_to_rels(root, qual,
-									below_outer_join,
-									JOIN_INNER,
-									security_level,
-									qualscope,
-									qualscope,
-									NULL,
-									NULL);
-		}
+		/*
+		 * We cheat to the extent of passing ojscope = qualscope rather than
+		 * its more logical value of NULL.  The only effect this has is to
+		 * force a Var-free qual to be evaluated at the rel rather than being
+		 * pushed up to top of tree, which we don't want.
+		 */
+		distribute_quals_to_rels(root, qualset,
+								 below_outer_join,
+								 NULL,
+								 security_level,
+								 qualscope,
+								 qualscope,
+								 NULL,
+								 NULL);
 		security_level++;
 	}
 
@@ -1572,6 +1673,38 @@ compute_semijoin_info(PlannerInfo *root, SpecialJoinInfo *sjinfo, List *clause)
  *
  *****************************************************************************/
 
+/*
+ * distribute_quals_to_rels
+ *	  Convenience routine to apply distribute_qual_to_rels to each element
+ *	  of an AND'ed list of clauses.
+ */
+static void
+distribute_quals_to_rels(PlannerInfo *root, List *clauses,
+						 bool below_outer_join,
+						 SpecialJoinInfo *sjinfo,
+						 Index security_level,
+						 Relids qualscope,
+						 Relids ojscope,
+						 Relids outerjoin_nonnullable,
+						 List **postponed_qual_list)
+{
+	ListCell   *lc;
+
+	foreach(lc, clauses)
+	{
+		Node	   *clause = (Node *) lfirst(lc);
+
+		distribute_qual_to_rels(root, clause,
+								below_outer_join,
+								sjinfo,
+								security_level,
+								qualscope,
+								ojscope,
+								outerjoin_nonnullable,
+								postponed_qual_list);
+	}
+}
+
 /*
  * distribute_qual_to_rels
  *	  Add clause information to either the baserestrictinfo or joininfo list
@@ -1586,7 +1719,7 @@ compute_semijoin_info(PlannerInfo *root, SpecialJoinInfo *sjinfo, List *clause)
  * 'clause': the qual clause to be distributed
  * 'below_outer_join': true if the qual is from a JOIN/ON that is below the
  *		nullable side of a higher-level outer join
- * 'jointype': type of join the qual is from (JOIN_INNER for a WHERE clause)
+ * 'sjinfo': join's SpecialJoinInfo (NULL for an inner join or WHERE clause)
  * 'security_level': security_level to assign to the qual
  * 'qualscope': set of baserels the qual's syntactic scope covers
  * 'ojscope': NULL if not an outer-join qual, else the minimum set of baserels
@@ -1604,12 +1737,13 @@ compute_semijoin_info(PlannerInfo *root, SpecialJoinInfo *sjinfo, List *clause)
  * level, which will be ojscope not necessarily qualscope.
  *
  * At the time this is called, root->join_info_list must contain entries for
- * all and only those special joins that are syntactically below this qual.
+ * all and only those special joins that are syntactically below this qual;
+ * in particular, the passed-in SpecialJoinInfo isn't yet in that list.
  */
 static void
 distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 						bool below_outer_join,
-						JoinType jointype,
+						SpecialJoinInfo *sjinfo,
 						Index security_level,
 						Relids qualscope,
 						Relids ojscope,
@@ -1646,7 +1780,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 		PostponedQual *pq = (PostponedQual *) palloc(sizeof(PostponedQual));
 
 		Assert(root->hasLateralRTEs);	/* shouldn't happen otherwise */
-		Assert(jointype == JOIN_INNER); /* mustn't postpone past outer join */
+		Assert(sjinfo == NULL); /* mustn't postpone past outer join */
 		pq->qual = clause;
 		pq->relids = relids;
 		*postponed_qual_list = lappend(*postponed_qual_list, pq);
@@ -1930,14 +2064,19 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 			/* we need to set up left_ec/right_ec the hard way */
 			initialize_mergeclause_eclasses(root, restrictinfo);
 			/* now see if it should go to any outer-join lists */
+			Assert(sjinfo != NULL);
 			if (bms_is_subset(restrictinfo->left_relids,
 							  outerjoin_nonnullable) &&
 				!bms_overlap(restrictinfo->right_relids,
 							 outerjoin_nonnullable))
 			{
 				/* we have outervar = innervar */
+				OuterJoinClauseInfo *ojcinfo = makeNode(OuterJoinClauseInfo);
+
+				ojcinfo->rinfo = restrictinfo;
+				ojcinfo->sjinfo = sjinfo;
 				root->left_join_clauses = lappend(root->left_join_clauses,
-												  restrictinfo);
+												  ojcinfo);
 				return;
 			}
 			if (bms_is_subset(restrictinfo->right_relids,
@@ -1946,15 +2085,23 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 							 outerjoin_nonnullable))
 			{
 				/* we have innervar = outervar */
+				OuterJoinClauseInfo *ojcinfo = makeNode(OuterJoinClauseInfo);
+
+				ojcinfo->rinfo = restrictinfo;
+				ojcinfo->sjinfo = sjinfo;
 				root->right_join_clauses = lappend(root->right_join_clauses,
-												   restrictinfo);
+												   ojcinfo);
 				return;
 			}
-			if (jointype == JOIN_FULL)
+			if (sjinfo->jointype == JOIN_FULL)
 			{
 				/* FULL JOIN (above tests cannot match in this case) */
+				OuterJoinClauseInfo *ojcinfo = makeNode(OuterJoinClauseInfo);
+
+				ojcinfo->rinfo = restrictinfo;
+				ojcinfo->sjinfo = sjinfo;
 				root->full_join_clauses = lappend(root->full_join_clauses,
-												  restrictinfo);
+												  ojcinfo);
 				return;
 			}
 			/* nope, so fall through to distribute_restrictinfo_to_rels */
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 2a07502030..59be25675f 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -40,6 +40,20 @@ typedef struct
 	int			win_location;
 } locate_windowfunc_context;
 
+typedef struct
+{
+	const Bitmapset *target_relids;
+	const Bitmapset *added_relids;
+	int			sublevels_up;
+} add_nulling_relids_context;
+
+typedef struct
+{
+	const Bitmapset *removable_relids;
+	const Bitmapset *except_relids;
+	int			sublevels_up;
+} remove_nulling_relids_context;
+
 static bool contain_aggs_of_level_walker(Node *node,
 										 contain_aggs_of_level_context *context);
 static bool locate_agg_of_level_walker(Node *node,
@@ -50,6 +64,10 @@ static bool locate_windowfunc_walker(Node *node,
 static bool checkExprHasSubLink_walker(Node *node, void *context);
 static Relids offset_relid_set(Relids relids, int offset);
 static Relids adjust_relid_set(Relids relids, int oldrelid, int newrelid);
+static Node *add_nulling_relids_mutator(Node *node,
+										add_nulling_relids_context *context);
+static Node *remove_nulling_relids_mutator(Node *node,
+										   remove_nulling_relids_context *context);
 
 
 /*
@@ -381,6 +399,8 @@ OffsetVarNodes_walker(Node *node, OffsetVarNodes_context *context)
 		if (var->varlevelsup == context->sublevels_up)
 		{
 			var->varno += context->offset;
+			var->varnullingrels = offset_relid_set(var->varnullingrels,
+												   context->offset);
 			if (var->varnosyn > 0)
 				var->varnosyn += context->offset;
 		}
@@ -419,6 +439,8 @@ OffsetVarNodes_walker(Node *node, OffsetVarNodes_context *context)
 		{
 			phv->phrels = offset_relid_set(phv->phrels,
 										   context->offset);
+			phv->phnullingrels = offset_relid_set(phv->phnullingrels,
+												  context->offset);
 		}
 		/* fall through to examine children */
 	}
@@ -543,11 +565,13 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
 	{
 		Var		   *var = (Var *) node;
 
-		if (var->varlevelsup == context->sublevels_up &&
-			var->varno == context->rt_index)
+		if (var->varlevelsup == context->sublevels_up)
 		{
-			var->varno = context->new_index;
-			/* If the syntactic referent is same RTE, fix it too */
+			if (var->varno == context->rt_index)
+				var->varno = context->new_index;
+			var->varnullingrels = adjust_relid_set(var->varnullingrels,
+												   context->rt_index,
+												   context->new_index);
 			if (var->varnosyn == context->rt_index)
 				var->varnosyn = context->new_index;
 		}
@@ -590,6 +614,9 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
 			phv->phrels = adjust_relid_set(phv->phrels,
 										   context->rt_index,
 										   context->new_index);
+			phv->phnullingrels = adjust_relid_set(phv->phnullingrels,
+												  context->rt_index,
+												  context->new_index);
 		}
 		/* fall through to examine children */
 	}
@@ -866,7 +893,8 @@ rangeTableEntry_used_walker(Node *node,
 		Var		   *var = (Var *) node;
 
 		if (var->varlevelsup == context->sublevels_up &&
-			var->varno == context->rt_index)
+			(var->varno == context->rt_index ||
+			 bms_is_member(context->rt_index, var->varnullingrels)))
 			return true;
 		return false;
 	}
@@ -1094,6 +1122,195 @@ AddInvertedQual(Query *parsetree, Node *qual)
 }
 
 
+/*
+ * add_nulling_relids() finds Vars and PlaceHolderVars that belong to any
+ * of the target_relids, and adds added_relids to their varnullingrels
+ * and phnullingrels fields.
+ */
+Node *
+add_nulling_relids(Node *node,
+				   const Bitmapset *target_relids,
+				   const Bitmapset *added_relids)
+{
+	add_nulling_relids_context context;
+
+	context.target_relids = target_relids;
+	context.added_relids = added_relids;
+	context.sublevels_up = 0;
+	return query_or_expression_tree_mutator(node,
+											add_nulling_relids_mutator,
+											&context,
+											0);
+}
+
+static Node *
+add_nulling_relids_mutator(Node *node,
+						   add_nulling_relids_context *context)
+{
+	if (node == NULL)
+		return NULL;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+
+		if (var->varlevelsup == context->sublevels_up &&
+			bms_is_member(var->varno, context->target_relids))
+		{
+			Relids		newnullingrels = bms_union(var->varnullingrels,
+												   context->added_relids);
+
+			/* Copy the Var ... */
+			var = copyObject(var);
+			/* ... and replace the copy's varnullingrels field */
+			var->varnullingrels = newnullingrels;
+			return (Node *) var;
+		}
+		/* Otherwise fall through to copy the Var normally */
+	}
+	else if (IsA(node, PlaceHolderVar))
+	{
+		PlaceHolderVar *phv = (PlaceHolderVar *) node;
+
+		if (phv->phlevelsup == context->sublevels_up &&
+			bms_overlap(phv->phrels, context->target_relids))
+		{
+			Relids		newnullingrels = bms_union(phv->phnullingrels,
+												   context->added_relids);
+
+			/*
+			 * We don't modify the contents of the PHV's expression, only add
+			 * to phnullingrels.  This corresponds to assuming that the PHV
+			 * will be evaluated at the same level as before, then perhaps be
+			 * nulled as it bubbles up.  Hence, just flat-copy the node ...
+			 */
+			phv = makeNode(PlaceHolderVar);
+			memcpy(phv, node, sizeof(PlaceHolderVar));
+			/* ... and replace the copy's phnullingrels field */
+			phv->phnullingrels = newnullingrels;
+			return (Node *) phv;
+		}
+		/* Otherwise fall through to copy the PlaceHolderVar normally */
+	}
+	else if (IsA(node, Query))
+	{
+		/* Recurse into RTE or sublink subquery */
+		Query	   *newnode;
+
+		context->sublevels_up++;
+		newnode = query_tree_mutator((Query *) node,
+									 add_nulling_relids_mutator,
+									 (void *) context,
+									 0);
+		context->sublevels_up--;
+		return (Node *) newnode;
+	}
+	return expression_tree_mutator(node, add_nulling_relids_mutator,
+								   (void *) context);
+}
+
+/*
+ * remove_nulling_relids() removes mentions of the specified RT index(es)
+ * in Var.varnullingrels and PlaceHolderVar.phnullingrels fields within
+ * the given expression, except in nodes belonging to rels listed in
+ * except_relids.
+ */
+Node *
+remove_nulling_relids(Node *node,
+					  const Bitmapset *removable_relids,
+					  const Bitmapset *except_relids)
+{
+	remove_nulling_relids_context context;
+
+	context.removable_relids = removable_relids;
+	context.except_relids = except_relids;
+	context.sublevels_up = 0;
+	return query_or_expression_tree_mutator(node,
+											remove_nulling_relids_mutator,
+											&context,
+											0);
+}
+
+static Node *
+remove_nulling_relids_mutator(Node *node,
+							  remove_nulling_relids_context *context)
+{
+	if (node == NULL)
+		return NULL;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+
+		if (var->varlevelsup == context->sublevels_up &&
+			!bms_is_member(var->varno, context->except_relids) &&
+			bms_overlap(var->varnullingrels, context->removable_relids))
+		{
+			Relids		newnullingrels = bms_difference(var->varnullingrels,
+														context->removable_relids);
+
+			/* Micro-optimization: ensure nullingrels is NULL if empty */
+			if (bms_is_empty(newnullingrels))
+				newnullingrels = NULL;
+			/* Copy the Var ... */
+			var = copyObject(var);
+			/* ... and replace the copy's varnullingrels field */
+			var->varnullingrels = newnullingrels;
+			return (Node *) var;
+		}
+		/* Otherwise fall through to copy the Var normally */
+	}
+	else if (IsA(node, PlaceHolderVar))
+	{
+		PlaceHolderVar *phv = (PlaceHolderVar *) node;
+
+		if (phv->phlevelsup == context->sublevels_up &&
+			!bms_overlap(phv->phrels, context->except_relids))
+		{
+			Relids		newnullingrels = bms_difference(phv->phnullingrels,
+														context->removable_relids);
+
+			/*
+			 * Micro-optimization: ensure nullingrels is NULL if empty.
+			 *
+			 * Note: it might seem desirable to remove the PHV altogether if
+			 * phnullingrels goes to empty.  Currently we dare not do that
+			 * because we use PHVs in some cases to enforce separate identity
+			 * of subexpressions; see wrap_non_vars usages in prepjointree.c.
+			 */
+			if (bms_is_empty(newnullingrels))
+				newnullingrels = NULL;
+			/* Copy the PlaceHolderVar and mutate what's below ... */
+			phv = (PlaceHolderVar *)
+				expression_tree_mutator(node,
+										remove_nulling_relids_mutator,
+										(void *) context);
+			/* ... and replace the copy's phnullingrels field */
+			phv->phnullingrels = newnullingrels;
+			/* We must also update phrels, if it contains a removable RTI */
+			phv->phrels = bms_difference(phv->phrels,
+										 context->removable_relids);
+			Assert(!bms_is_empty(phv->phrels));
+			return (Node *) phv;
+		}
+		/* Otherwise fall through to copy the PlaceHolderVar normally */
+	}
+	else if (IsA(node, Query))
+	{
+		/* Recurse into RTE or sublink subquery */
+		Query	   *newnode;
+
+		context->sublevels_up++;
+		newnode = query_tree_mutator((Query *) node,
+									 remove_nulling_relids_mutator,
+									 (void *) context,
+									 0);
+		context->sublevels_up--;
+		return (Node *) newnode;
+	}
+	return expression_tree_mutator(node, remove_nulling_relids_mutator,
+								   (void *) context);
+}
+
+
 /*
  * replace_rte_variables() finds all Vars in an expression tree
  * that reference a particular RTE, and replaces them with substitute
diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/utils/misc/queryjumble.c
index 0ace74de78..6ae8f0ece7 100644
--- a/src/backend/utils/misc/queryjumble.c
+++ b/src/backend/utils/misc/queryjumble.c
@@ -383,6 +383,11 @@ JumbleExpr(JumbleState *jstate, Node *node)
 				APP_JUMB(var->varno);
 				APP_JUMB(var->varattno);
 				APP_JUMB(var->varlevelsup);
+
+				/*
+				 * We can omit varnullingrels, because it's fully determined
+				 * by varno/varlevelsup plus the Var's query location.
+				 */
 			}
 			break;
 		case T_Const:
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 654dba61aa..287bd554f6 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -249,10 +249,8 @@ struct PlannerInfo
 	struct AppendRelInfo **append_rel_array pg_node_attr(read_write_ignore);
 
 	/*
-	 * all_baserels is a Relids set of all base relids (but not "other"
-	 * relids) in the query; that is, the Relids identifier of the final join
-	 * we need to form.  This is computed in make_one_rel, just before we
-	 * start making Paths.
+	 * all_baserels is a Relids set of all base relids (but not joins or
+	 * "other" rels) in the query.  This is computed in deconstruct_jointree.
 	 */
 	Relids		all_baserels;
 
@@ -313,19 +311,19 @@ struct PlannerInfo
 	List	   *canon_pathkeys;
 
 	/*
-	 * list of RestrictInfos for mergejoinable outer join clauses
+	 * list of OuterJoinClauseInfos for mergejoinable outer join clauses
 	 * w/nonnullable var on left
 	 */
 	List	   *left_join_clauses;
 
 	/*
-	 * list of RestrictInfos for mergejoinable outer join clauses
+	 * list of OuterJoinClauseInfos for mergejoinable outer join clauses
 	 * w/nonnullable var on right
 	 */
 	List	   *right_join_clauses;
 
 	/*
-	 * list of RestrictInfos for mergejoinable full join clauses
+	 * list of OuterJoinClauseInfos for mergejoinable full join clauses
 	 */
 	List	   *full_join_clauses;
 
@@ -883,7 +881,7 @@ typedef struct RelOptInfo
 	int32	   *attr_widths pg_node_attr(read_write_ignore);
 	/* LATERAL Vars and PHVs referenced by rel */
 	List	   *lateral_vars;
-	/* rels that reference me laterally */
+	/* rels that reference this baserel laterally */
 	Relids		lateral_referencers;
 	/* list of IndexOptInfo */
 	List	   *indexlist;
@@ -893,10 +891,7 @@ typedef struct RelOptInfo
 	BlockNumber pages;
 	Cardinality tuples;
 	double		allvisfrac;
-
-	/*
-	 * Indexes in PlannerInfo's eq_classes list of ECs that mention this rel
-	 */
+	/* indexes in PlannerInfo's eq_classes list of ECs that mention this rel */
 	Bitmapset  *eclass_indexes;
 	PlannerInfo *subroot;		/* if subquery */
 	List	   *subplan_params; /* if subquery */
@@ -2596,10 +2591,15 @@ typedef struct MergeScanSelCache
  * of a plan tree.  This is used during planning to represent the contained
  * expression.  At the end of the planning process it is replaced by either
  * the contained expression or a Var referring to a lower-level evaluation of
- * the contained expression.  Typically the evaluation occurs below an outer
+ * the contained expression.  Generally the evaluation occurs below an outer
  * join, and Var references above the outer join might thereby yield NULL
  * instead of the expression value.
  *
+ * phrels and phlevelsup correspond to the varno/varlevelsup fields of a
+ * plain Var, except that phrels has to be a relid set since the evaluation
+ * level of a PlaceHolderVar might be a join rather than a base relation.
+ * Likewise, phnullingrels corresponds to varnullingrels.
+ *
  * Although the planner treats this as an expression node type, it is not
  * recognized by the parser or executor, so we declare it here rather than
  * in primnodes.h.
@@ -2612,8 +2612,10 @@ typedef struct MergeScanSelCache
  * PHV.  Another way in which it can happen is that initplan sublinks
  * could get replaced by differently-numbered Params when sublink folding
  * is done.  (The end result of such a situation would be some
- * unreferenced initplans, which is annoying but not really a problem.) On
- * the same reasoning, there is no need to examine phrels.
+ * unreferenced initplans, which is annoying but not really a problem.)
+ * On the same reasoning, there is no need to examine phrels.  But we do
+ * need to compare phnullingrels, as that represents effects that are
+ * external to the original value of the PHV.
  */
 
 typedef struct PlaceHolderVar
@@ -2623,9 +2625,12 @@ typedef struct PlaceHolderVar
 	/* the represented expression */
 	Expr	   *phexpr pg_node_attr(equal_ignore);
 
-	/* base relids syntactically within expr src */
+	/* base+OJ relids syntactically within expr src */
 	Relids		phrels pg_node_attr(equal_ignore);
 
+	/* RT indexes of outer joins that can null PHV's value */
+	Relids		phnullingrels;
+
 	/* ID for PHV (unique within planner run) */
 	Index		phid;
 
@@ -2714,6 +2719,21 @@ struct SpecialJoinInfo
 	List	   *semi_rhs_exprs; /* righthand-side expressions of these ops */
 };
 
+/*
+ * Transient outer-join clause info.
+ *
+ * We set aside every outer join ON clause that looks mergejoinable,
+ * and process it specially at the end of qual distribution.
+ */
+typedef struct OuterJoinClauseInfo
+{
+	pg_node_attr(no_copy_equal, no_read)
+
+	NodeTag		type;
+	RestrictInfo *rinfo;		/* a mergejoinable outer-join clause */
+	SpecialJoinInfo *sjinfo;	/* the outer join's SpecialJoinInfo */
+} OuterJoinClauseInfo;
+
 /*
  * Append-relation info.
  *
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 74f228d959..716d939abf 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -180,6 +180,14 @@ typedef struct Expr
  * row identity information during UPDATE/DELETE/MERGE.  This value should
  * never be seen outside the planner.
  *
+ * varnullingrels is the set of RT indexes of outer joins that can force
+ * the Var's value to null (at the point where it appears in the query).
+ * See optimizer/README for discussion of that.
+ *
+ * varlevelsup is greater than zero in Vars that represent outer references.
+ * Note that it affects the meaning of all of varno, varnullingrels, and
+ * varnosyn, all of which refer to the range table of that query level.
+ *
  * In the parser, varnosyn and varattnosyn are either identical to
  * varno/varattno, or they specify the column's position in an aliased JOIN
  * RTE that hides the semantic referent RTE's refname.  This is a syntactic
@@ -222,6 +230,8 @@ typedef struct Var
 	int32		vartypmod;
 	/* OID of collation, or InvalidOid if none */
 	Oid			varcollid;
+	/* RT indexes of outer joins that can replace the Var's value with null */
+	Bitmapset  *varnullingrels;
 
 	/*
 	 * for subquery variables referencing outer relations; 0 in a normal var,
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index 05e6fe1f4b..a77e9980ed 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -65,6 +65,13 @@ extern bool contain_windowfuncs(Node *node);
 extern int	locate_windowfunc(Node *node);
 extern bool checkExprHasSubLink(Node *node);
 
+extern Node *add_nulling_relids(Node *node,
+								const Bitmapset *target_relids,
+								const Bitmapset *added_relids);
+extern Node *remove_nulling_relids(Node *node,
+								   const Bitmapset *removable_relids,
+								   const Bitmapset *except_relids);
+
 extern Node *replace_rte_variables(Node *node,
 								   int target_varno, int sublevels_up,
 								   replace_rte_variables_callback callback,
