commit d604b10cd3e6e044a551e6a18b0a520d35916e68
Author: Tom Lane <tgl@sss.pgh.pa.us>
Date:   Fri Dec 23 11:28:14 2022 -0500

    Remove the outerjoin_delayed mechanism.
    
    We needed this before to prevent quals from getting evaluated below
    outer joins that should null some of their vars.  Now that we consider
    varnullingrels while placing quals, that's taken care of
    automatically, so throw the whole thing away.
    
    This results in one cosmetic change in the regression test outputs,
    where clauses that were not previously considered to be
    EquivalenceClass candidates are now treated as ECs, and the EC
    machinery chooses to emit an equivalent but not identical set of
    clauses.

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 12e940a2e6..47ce73ff57 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -6305,7 +6305,6 @@ foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel,
 									  expr,
 									  true,
 									  false,
-									  false,
 									  root->qual_security_level,
 									  grouped_rel->relids,
 									  NULL);
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index d9b76ecab4..bcf7fcf21c 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -200,7 +200,6 @@ process_equivalence(PlannerInfo *root,
 				make_restrictinfo(root,
 								  (Expr *) ntest,
 								  restrictinfo->is_pushed_down,
-								  restrictinfo->outerjoin_delayed,
 								  restrictinfo->pseudoconstant,
 								  restrictinfo->security_level,
 								  NULL,
@@ -1144,11 +1143,8 @@ generate_base_implied_equalities_const(PlannerInfo *root,
 	{
 		RestrictInfo *restrictinfo = (RestrictInfo *) linitial(ec->ec_sources);
 
-		if (bms_membership(restrictinfo->required_relids) != BMS_MULTIPLE)
-		{
-			distribute_restrictinfo_to_rels(root, restrictinfo);
-			return;
-		}
+		distribute_restrictinfo_to_rels(root, restrictinfo);
+		return;
 	}
 
 	/*
@@ -1959,15 +1955,6 @@ create_join_clause(PlannerInfo *root,
  * seen as a clauseless join and avoided during join order searching.
  * We handle this by generating a constant-TRUE clause that is marked with
  * required_relids that make it a join between the correct relations.
- *
- * Outer join clauses that are marked outerjoin_delayed are special: this
- * condition means that one or both VARs might go to null due to a lower
- * outer join.  We can still push a constant through the clause, but only
- * if its operator is strict; and we *have to* throw the clause back into
- * regular joinclause processing.  By keeping the strict join clause,
- * we ensure that any null-extended rows that are mistakenly generated due
- * to suppressing rows not matching the constant will be rejected at the
- * upper outer join.  (This doesn't work for full-join clauses.)
  */
 void
 reconsider_outer_join_clauses(PlannerInfo *root)
@@ -1997,7 +1984,6 @@ reconsider_outer_join_clauses(PlannerInfo *root)
 				rinfo = make_restrictinfo(root,
 										  (Expr *) makeBoolConst(true, false),
 										  true, /* is_pushed_down */
-										  false,	/* outerjoin_delayed */
 										  false,	/* pseudoconstant */
 										  0,	/* security_level */
 										  rinfo->required_relids,
@@ -2023,7 +2009,6 @@ reconsider_outer_join_clauses(PlannerInfo *root)
 				rinfo = make_restrictinfo(root,
 										  (Expr *) makeBoolConst(true, false),
 										  true, /* is_pushed_down */
-										  false,	/* outerjoin_delayed */
 										  false,	/* pseudoconstant */
 										  0,	/* security_level */
 										  rinfo->required_relids,
@@ -2049,7 +2034,6 @@ reconsider_outer_join_clauses(PlannerInfo *root)
 				rinfo = make_restrictinfo(root,
 										  (Expr *) makeBoolConst(true, false),
 										  true, /* is_pushed_down */
-										  false,	/* outerjoin_delayed */
 										  false,	/* pseudoconstant */
 										  0,	/* security_level */
 										  rinfo->required_relids,
@@ -2104,10 +2088,6 @@ reconsider_outer_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo,
 	opno = ((OpExpr *) rinfo->clause)->opno;
 	collation = ((OpExpr *) rinfo->clause)->inputcollid;
 
-	/* If clause is outerjoin_delayed, operator must be strict */
-	if (rinfo->outerjoin_delayed && !op_strict(opno))
-		return false;
-
 	/* Extract needed info from the clause */
 	op_input_types(opno, &left_type, &right_type);
 	if (outer_on_left)
@@ -2224,10 +2204,6 @@ reconsider_full_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo)
 				right_relids;
 	ListCell   *lc1;
 
-	/* Can't use an outerjoin_delayed clause here */
-	if (rinfo->outerjoin_delayed)
-		return false;
-
 	/* Extract needed info from the clause */
 	Assert(is_opclause(rinfo->clause));
 	opno = ((OpExpr *) rinfo->clause)->opno;
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index c9b9cc3f74..6fe00652b6 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -130,9 +130,6 @@ static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 									bool is_clone,
 									List **postponed_qual_list,
 									List **postponed_oj_qual_list);
-static bool check_outerjoin_delay(PlannerInfo *root, Relids *relids_p);
-static bool check_equivalence_delay(PlannerInfo *root,
-									RestrictInfo *restrictinfo);
 static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause);
 static void check_mergejoinable(RestrictInfo *restrictinfo);
 static void check_hashjoinable(RestrictInfo *restrictinfo);
@@ -738,15 +735,6 @@ create_lateral_join_info(PlannerInfo *root)
  * A sub-joinlist represents a subproblem to be planned separately. Currently
  * sub-joinlists arise only from FULL OUTER JOIN or when collapsing of
  * subproblems is stopped by join_collapse_limit or from_collapse_limit.
- *
- * NOTE: when dealing with inner joins, it is appropriate to let a qual clause
- * be evaluated at the lowest level where all the variables it mentions are
- * available.  However, we cannot push a qual down into the nullable side(s)
- * of an outer join since the qual might eliminate matching rows and cause a
- * NULL row to be incorrectly emitted by the join.  Therefore, we artificially
- * OR the minimum-relids of such an outer join into the required_relids of
- * clauses appearing above it.  This forces those clauses to be delayed until
- * application of the outer join (or maybe even higher in the join tree).
  */
 List *
 deconstruct_jointree(PlannerInfo *root)
@@ -758,9 +746,8 @@ deconstruct_jointree(PlannerInfo *root)
 
 	/*
 	 * After this point, no more PlaceHolderInfos may be made, because
-	 * make_outerjoininfo and update_placeholder_eval_levels require all
-	 * active placeholders to be present in root->placeholder_list while we
-	 * crawl up the join tree.
+	 * make_outerjoininfo requires all active placeholders to be present in
+	 * root->placeholder_list while we crawl up the join tree.
 	 */
 	root->placeholdersFrozen = true;
 
@@ -798,31 +785,12 @@ deconstruct_jointree(PlannerInfo *root)
 	 */
 	if (root->join_info_list)
 	{
-		/*
-		 * XXX hack: when we call distribute_qual_to_rels to process one of
-		 * these clauses, neither the owning SpecialJoinInfo nor any later
-		 * ones can appear in root->join_info_list, else the wrong things will
-		 * happen.  Fake it out by emptying join_info_list and rebuilding it
-		 * as we go. This works because join_info_list is only appended to
-		 * during deconstruct_distribute, so we know we are examining
-		 * SpecialJoinInfos bottom-up, just like the first time.  We can get
-		 * rid of this hack later, after fixing things so that
-		 * distribute_qual_to_rels doesn't have that requirement about
-		 * join_info_list.
-		 */
-		root->join_info_list = NIL;
-
 		foreach(lc, item_list)
 		{
 			JoinTreeItem *jtitem = (JoinTreeItem *) lfirst(lc);
 
 			if (jtitem->oj_joinclauses != NIL)
 				deconstruct_distribute_oj_quals(root, item_list, jtitem);
-
-			/* XXX Rest of hack: rebuild join_info_list as we go */
-			if (jtitem->sjinfo)
-				root->join_info_list = lappend(root->join_info_list,
-											   jtitem->sjinfo);
 		}
 	}
 
@@ -1265,11 +1233,7 @@ deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem,
 
 		/* 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);
-		}
 	}
 	else
 	{
@@ -2155,8 +2119,8 @@ distribute_quals_to_rels(PlannerInfo *root, List *clauses,
  * 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;
- * in particular, the passed-in SpecialJoinInfo isn't yet in that list.
+ * at least those special joins that are syntactically below this qual.
+ * (We now need that only for detection of redundant IS NULL quals.)
  */
 static void
 distribute_qual_to_rels(PlannerInfo *root, Node *clause,
@@ -2174,7 +2138,6 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 {
 	Relids		relids;
 	bool		is_pushed_down;
-	bool		outerjoin_delayed;
 	bool		pseudoconstant = false;
 	bool		maybe_equivalence;
 	bool		maybe_outer_join;
@@ -2328,19 +2291,12 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 		maybe_equivalence = false;
 		maybe_outer_join = true;
 
-		/* Check to see if must be delayed by lower outer join */
-		outerjoin_delayed = check_outerjoin_delay(root,
-												  &relids);
-
 		/*
 		 * Now force the qual to be evaluated exactly at the level of joining
 		 * corresponding to the outer join.  We cannot let it get pushed down
 		 * into the nonnullable side, since then we'd produce no output rows,
 		 * rather than the intended single null-extended row, for any
 		 * nonnullable-side rows failing the qual.
-		 *
-		 * (Do this step after calling check_outerjoin_delay, because that
-		 * trashes relids.)
 		 */
 		Assert(ojscope);
 		relids = ojscope;
@@ -2354,32 +2310,16 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 		 */
 		is_pushed_down = true;
 
-		/* Check to see if must be delayed by lower outer join */
-		outerjoin_delayed = check_outerjoin_delay(root,
-												  &relids);
-
-		if (outerjoin_delayed)
-		{
-			/* Should still be a subset of current scope ... */
-			Assert(root->hasLateralRTEs || bms_is_subset(relids, qualscope));
-			Assert(ojscope == NULL || bms_is_subset(relids, ojscope));
-
-			/*
-			 * Because application of the qual will be delayed by outer join,
-			 * we mustn't assume its vars are equal everywhere.
-			 */
-			maybe_equivalence = false;
+		/*
+		 * It's possible that this is an IS NULL clause that's redundant with
+		 * a lower antijoin; if so we can just discard it.  We need not test
+		 * in any of the other cases, because this will only be possible for
+		 * pushed-down clauses.
+		 */
+		if (check_redundant_nullability_qual(root, clause))
+			return;
 
-			/*
-			 * It's possible that this is an IS NULL clause that's redundant
-			 * with a lower antijoin; if so we can just discard it.  We need
-			 * not test in any of the other cases, because this will only be
-			 * possible for pushed-down, delayed clauses.
-			 */
-			if (check_redundant_nullability_qual(root, clause))
-				return;
-		}
-		else if (!allow_equivalence)
+		if (!allow_equivalence)
 		{
 			/* Caller says it mustn't become an equivalence class */
 			maybe_equivalence = false;
@@ -2387,8 +2327,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 		else
 		{
 			/*
-			 * Qual is not delayed by any lower outer-join restriction, so we
-			 * can consider feeding it to the equivalence machinery. However,
+			 * Consider feeding qual to the equivalence machinery.  However,
 			 * if it's itself within an outer-join clause, treat it as though
 			 * it appeared below that outer join (note that we can only get
 			 * here when the clause references only nullable-side rels).
@@ -2411,7 +2350,6 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	restrictinfo = make_restrictinfo(root,
 									 (Expr *) clause,
 									 is_pushed_down,
-									 outerjoin_delayed,
 									 pseudoconstant,
 									 security_level,
 									 relids,
@@ -2463,6 +2401,8 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	check_mergejoinable(restrictinfo);
 
 	/*
+	 * XXX rewrite:
+	 *
 	 * If it is a true equivalence clause, send it to the EquivalenceClass
 	 * machinery.  We do *not* attach it directly to any restriction or join
 	 * lists.  The EC code will propagate it to the appropriate places later.
@@ -2498,8 +2438,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	{
 		if (maybe_equivalence)
 		{
-			if (check_equivalence_delay(root, restrictinfo) &&
-				process_equivalence(root, &restrictinfo, below_outer_join))
+			if (process_equivalence(root, &restrictinfo, below_outer_join))
 				return;
 			/* EC rejected it, so set left_ec/right_ec the hard way ... */
 			if (restrictinfo->mergeopfamilies)	/* EC might have changed this */
@@ -2564,125 +2503,6 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	distribute_restrictinfo_to_rels(root, restrictinfo);
 }
 
-/*
- * check_outerjoin_delay
- *		Detect whether a qual referencing the given relids must be delayed
- *		in application due to the presence of a lower outer join.
- *
- * If the qual must be delayed, add relids to *relids_p to reflect the lowest
- * safe level for evaluating the qual, and return true.
- *
- * For an is_pushed_down qual, we can evaluate the qual as soon as (1) we have
- * all the rels it mentions, and (2) we are at or above any outer joins that
- * can null any of these rels and are below the syntactic location of the
- * given qual.  We must enforce (2) because pushing down such a clause below
- * the OJ might cause the OJ to emit null-extended rows that should not have
- * been formed, or that should have been rejected by the clause.  (This is
- * only an issue for non-strict quals, since if we can prove a qual mentioning
- * only nullable rels is strict, we'd have reduced the outer join to an inner
- * join in reduce_outer_joins().)
- *
- * To enforce (2), scan the join_info_list and merge the required-relid sets of
- * any such OJs into the clause's own reference list.  At the time we are
- * called, the join_info_list contains only outer joins below this qual.  We
- * have to repeat the scan until no new relids get added; this ensures that
- * the qual is suitably delayed regardless of the order in which OJs get
- * executed.  As an example, if we have one OJ with LHS=A, RHS=B, and one with
- * LHS=B, RHS=C, it is implied that these can be done in either order; if the
- * B/C join is done first then the join to A can null C, so a qual actually
- * mentioning only C cannot be applied below the join to A.
- *
- * For a non-pushed-down qual, this isn't going to determine where we place the
- * qual, but we need to determine outerjoin_delayed anyway for use later in
- * the planning process.
- */
-static bool
-check_outerjoin_delay(PlannerInfo *root,
-					  Relids *relids_p) /* in/out parameter */
-{
-	Relids		relids;
-	bool		outerjoin_delayed;
-	bool		found_some;
-
-	/* fast path if no special joins */
-	if (root->join_info_list == NIL)
-		return false;
-
-	/* must copy relids because we need the original value at the end */
-	relids = bms_copy(*relids_p);
-	outerjoin_delayed = false;
-	do
-	{
-		ListCell   *l;
-
-		found_some = false;
-		foreach(l, root->join_info_list)
-		{
-			SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(l);
-
-			/* do we reference any nullable rels of this OJ? */
-			if (bms_overlap(relids, sjinfo->min_righthand) ||
-				(sjinfo->jointype == JOIN_FULL &&
-				 bms_overlap(relids, sjinfo->min_lefthand)))
-			{
-				/* yes; have we included all its rels in relids? */
-				if (!bms_is_subset(sjinfo->min_lefthand, relids) ||
-					!bms_is_subset(sjinfo->min_righthand, relids))
-				{
-					/* no, so add them in */
-					relids = bms_add_members(relids, sjinfo->min_lefthand);
-					relids = bms_add_members(relids, sjinfo->min_righthand);
-					outerjoin_delayed = true;
-					/* we'll need another iteration */
-					found_some = true;
-				}
-			}
-		}
-	} while (found_some);
-
-	/* replace *relids_p */
-	bms_free(*relids_p);
-	*relids_p = relids;
-	return outerjoin_delayed;
-}
-
-/*
- * check_equivalence_delay
- *		Detect whether a potential equivalence clause is rendered unsafe
- *		by outer-join-delay considerations.  Return true if it's safe.
- *
- * The initial tests in distribute_qual_to_rels will consider a mergejoinable
- * clause to be a potential equivalence clause if it is not outerjoin_delayed.
- * But since the point of equivalence processing is that we will recombine the
- * two sides of the clause with others, we have to check that each side
- * satisfies the not-outerjoin_delayed condition on its own; otherwise it might
- * not be safe to evaluate everywhere we could place a derived equivalence
- * condition.
- */
-static bool
-check_equivalence_delay(PlannerInfo *root,
-						RestrictInfo *restrictinfo)
-{
-	Relids		relids;
-
-	/* fast path if no special joins */
-	if (root->join_info_list == NIL)
-		return true;
-
-	/* must copy restrictinfo's relids to avoid changing it */
-	relids = bms_copy(restrictinfo->left_relids);
-	/* check left side does not need delay */
-	if (check_outerjoin_delay(root, &relids))
-		return false;
-
-	/* and similarly for the right side */
-	relids = bms_copy(restrictinfo->right_relids);
-	if (check_outerjoin_delay(root, &relids))
-		return false;
-
-	return true;
-}
-
 /*
  * check_redundant_nullability_qual
  *	  Check to see if the qual is an IS NULL qual that is redundant with
@@ -2697,25 +2517,33 @@ static bool
 check_redundant_nullability_qual(PlannerInfo *root, Node *clause)
 {
 	Var		   *forced_null_var;
-	Index		forced_null_rel;
 	ListCell   *lc;
 
 	/* Check for IS NULL, and identify the Var forced to NULL */
 	forced_null_var = find_forced_null_var(clause);
 	if (forced_null_var == NULL)
 		return false;
-	forced_null_rel = forced_null_var->varno;
 
 	/*
 	 * If the Var comes from the nullable side of a lower antijoin, the IS
-	 * NULL condition is necessarily true.
+	 * NULL condition is necessarily true.  If it's not nulled by anything,
+	 * there is no point in searching the join_info_list.  Otherwise, we need
+	 * to find out whether the nulling rel is an antijoin.
 	 */
+	if (forced_null_var->varnullingrels == NULL)
+		return false;
+
 	foreach(lc, root->join_info_list)
 	{
 		SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc);
 
-		if (sjinfo->jointype == JOIN_ANTI &&
-			bms_is_member(forced_null_rel, sjinfo->syn_righthand))
+		/*
+		 * This test will not succeed if sjinfo->ojrelid is zero, which is
+		 * possible for an antijoin that was converted from a semijoin; but in
+		 * such a case the Var couldn't have come from its nullable side.
+		 */
+		if (sjinfo->jointype == JOIN_ANTI && sjinfo->ojrelid != 0 &&
+			bms_is_member(sjinfo->ojrelid, forced_null_var->varnullingrels))
 			return true;
 	}
 
@@ -2907,7 +2735,6 @@ process_implied_equality(PlannerInfo *root,
 	restrictinfo = make_restrictinfo(root,
 									 (Expr *) clause,
 									 true,	/* is_pushed_down */
-									 false, /* outerjoin_delayed */
 									 pseudoconstant,
 									 security_level,
 									 relids,
@@ -2999,7 +2826,6 @@ build_implied_join_equality(PlannerInfo *root,
 	restrictinfo = make_restrictinfo(root,
 									 clause,
 									 true,	/* is_pushed_down */
-									 false, /* outerjoin_delayed */
 									 false, /* pseudoconstant */
 									 security_level,	/* security_level */
 									 qualscope, /* required_relids */
@@ -3071,8 +2897,7 @@ match_foreign_keys_to_quals(PlannerInfo *root)
 		 * Note: for simple inner joins, any match should be in an eclass.
 		 * "Loose" quals that syntactically match an FK equality must have
 		 * been rejected for EC status because they are outer-join quals or
-		 * similar.  We can still consider them to match the FK if they are
-		 * not outerjoin_delayed.
+		 * similar.  We can still consider them to match the FK.
 		 */
 		for (colno = 0; colno < fkinfo->nkeys; colno++)
 		{
@@ -3107,10 +2932,6 @@ match_foreign_keys_to_quals(PlannerInfo *root)
 				Var		   *leftvar;
 				Var		   *rightvar;
 
-				/* Ignore outerjoin-delayed clauses */
-				if (rinfo->outerjoin_delayed)
-					continue;
-
 				/* Only binary OpExprs are useful for consideration */
 				if (!IsA(clause, OpExpr) ||
 					list_length(clause->args) != 2)
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 6814377599..0806ac78fa 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -866,7 +866,6 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 								 make_restrictinfo(root,
 												   (Expr *) onecq,
 												   rinfo->is_pushed_down,
-												   rinfo->outerjoin_delayed,
 												   pseudoconstant,
 												   rinfo->security_level,
 												   NULL, NULL));
@@ -902,7 +901,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 				/* not likely that we'd see constants here, so no check */
 				childquals = lappend(childquals,
 									 make_restrictinfo(root, qual,
-													   true, false, false,
+													   true, false,
 													   security_level,
 													   NULL, NULL));
 				cq_min_security = Min(cq_min_security, security_level);
diff --git a/src/backend/optimizer/util/orclauses.c b/src/backend/optimizer/util/orclauses.c
index e02dabe2ee..50e93f0a62 100644
--- a/src/backend/optimizer/util/orclauses.c
+++ b/src/backend/optimizer/util/orclauses.c
@@ -267,7 +267,6 @@ consider_new_or_clause(PlannerInfo *root, RelOptInfo *rel,
 								 orclause,
 								 true,
 								 false,
-								 false,
 								 join_or_rinfo->security_level,
 								 NULL,
 								 NULL);
diff --git a/src/backend/optimizer/util/placeholder.c b/src/backend/optimizer/util/placeholder.c
index 25d4c54400..4435fd9090 100644
--- a/src/backend/optimizer/util/placeholder.c
+++ b/src/backend/optimizer/util/placeholder.c
@@ -134,7 +134,6 @@ find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv)
 		phinfo->ph_eval_at = bms_copy(phv->phrels);
 		Assert(!bms_is_empty(phinfo->ph_eval_at));
 	}
-	/* ph_eval_at may change later, see update_placeholder_eval_levels */
 	phinfo->ph_needed = NULL;	/* initially it's unused */
 	/* for the moment, estimate width using just the datatype info */
 	phinfo->ph_width = get_typavgwidth(exprType((Node *) phv->phexpr),
@@ -284,99 +283,6 @@ find_placeholders_in_expr(PlannerInfo *root, Node *expr)
 	list_free(vars);
 }
 
-/*
- * update_placeholder_eval_levels
- *		Adjust the target evaluation levels for placeholders
- *
- * The initial eval_at level set by find_placeholder_info was the set of
- * rels used in the placeholder's expression (or the whole subselect below
- * the placeholder's syntactic location, if the expr is variable-free).
- * If the query contains any outer joins that can null any of those rels,
- * we must delay evaluation to above those joins.
- *
- * We repeat this operation each time we add another outer join to
- * root->join_info_list.  It's somewhat annoying to have to do that, but
- * since we don't have very much information on the placeholders' locations,
- * it's hard to avoid.  Each placeholder's eval_at level must be correct
- * by the time it starts to figure in outer-join delay decisions for higher
- * outer joins.
- *
- * In future we might want to put additional policy/heuristics here to
- * try to determine an optimal evaluation level.  The current rules will
- * result in evaluation at the lowest possible level.  However, pushing a
- * placeholder eval up the tree is likely to further constrain evaluation
- * order for outer joins, so it could easily be counterproductive; and we
- * don't have enough information at this point to make an intelligent choice.
- */
-void
-update_placeholder_eval_levels(PlannerInfo *root, SpecialJoinInfo *new_sjinfo)
-{
-	ListCell   *lc1;
-
-	foreach(lc1, root->placeholder_list)
-	{
-		PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc1);
-		Relids		syn_level = phinfo->ph_var->phrels;
-		Relids		eval_at;
-		bool		found_some;
-		ListCell   *lc2;
-
-		/*
-		 * We don't need to do any work on this placeholder unless the
-		 * newly-added outer join is syntactically beneath its location.
-		 */
-		if (!bms_is_subset(new_sjinfo->syn_lefthand, syn_level) ||
-			!bms_is_subset(new_sjinfo->syn_righthand, syn_level))
-			continue;
-
-		/*
-		 * Check for delays due to lower outer joins.  This is the same logic
-		 * as in check_outerjoin_delay in initsplan.c.
-		 */
-		eval_at = phinfo->ph_eval_at;
-
-		do
-		{
-			found_some = false;
-			foreach(lc2, root->join_info_list)
-			{
-				SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc2);
-
-				/* disregard joins not within the PHV's sub-select */
-				if (!bms_is_subset(sjinfo->syn_lefthand, syn_level) ||
-					!bms_is_subset(sjinfo->syn_righthand, syn_level))
-					continue;
-
-				/* do we reference any nullable rels of this OJ? */
-				if (bms_overlap(eval_at, sjinfo->min_righthand) ||
-					(sjinfo->jointype == JOIN_FULL &&
-					 bms_overlap(eval_at, sjinfo->min_lefthand)))
-				{
-					/* yes; have we included all its rels in eval_at? */
-					if (!bms_is_subset(sjinfo->min_lefthand, eval_at) ||
-						!bms_is_subset(sjinfo->min_righthand, eval_at))
-					{
-						/* no, so add them in */
-						eval_at = bms_add_members(eval_at,
-												  sjinfo->min_lefthand);
-						eval_at = bms_add_members(eval_at,
-												  sjinfo->min_righthand);
-						if (sjinfo->ojrelid)
-							eval_at = bms_add_member(eval_at, sjinfo->ojrelid);
-						/* we'll need another iteration */
-						found_some = true;
-					}
-				}
-			}
-		} while (found_some);
-
-		/* Can't move the PHV's eval_at level to above its syntactic level */
-		Assert(bms_is_subset(eval_at, syn_level));
-
-		phinfo->ph_eval_at = eval_at;
-	}
-}
-
 /*
  * fix_placeholder_input_needed_levels
  *		Adjust the "needed at" levels for placeholder inputs
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index c3af845acd..301b41b278 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -25,7 +25,6 @@ static RestrictInfo *make_restrictinfo_internal(PlannerInfo *root,
 												Expr *clause,
 												Expr *orclause,
 												bool is_pushed_down,
-												bool outerjoin_delayed,
 												bool pseudoconstant,
 												Index security_level,
 												Relids required_relids,
@@ -33,7 +32,6 @@ static RestrictInfo *make_restrictinfo_internal(PlannerInfo *root,
 static Expr *make_sub_restrictinfos(PlannerInfo *root,
 									Expr *clause,
 									bool is_pushed_down,
-									bool outerjoin_delayed,
 									bool pseudoconstant,
 									Index security_level,
 									Relids required_relids,
@@ -45,7 +43,7 @@ static Expr *make_sub_restrictinfos(PlannerInfo *root,
  *
  * Build a RestrictInfo node containing the given subexpression.
  *
- * The is_pushed_down, outerjoin_delayed, and pseudoconstant flags for the
+ * The is_pushed_down and pseudoconstant flags for the
  * RestrictInfo must be supplied by the caller, as well as the correct values
  * for security_level and outer_relids.
  * required_relids can be NULL, in which case it defaults to the actual clause
@@ -63,7 +61,6 @@ RestrictInfo *
 make_restrictinfo(PlannerInfo *root,
 				  Expr *clause,
 				  bool is_pushed_down,
-				  bool outerjoin_delayed,
 				  bool pseudoconstant,
 				  Index security_level,
 				  Relids required_relids,
@@ -77,7 +74,6 @@ make_restrictinfo(PlannerInfo *root,
 		return (RestrictInfo *) make_sub_restrictinfos(root,
 													   clause,
 													   is_pushed_down,
-													   outerjoin_delayed,
 													   pseudoconstant,
 													   security_level,
 													   required_relids,
@@ -90,7 +86,6 @@ make_restrictinfo(PlannerInfo *root,
 									  clause,
 									  NULL,
 									  is_pushed_down,
-									  outerjoin_delayed,
 									  pseudoconstant,
 									  security_level,
 									  required_relids,
@@ -107,7 +102,6 @@ make_restrictinfo_internal(PlannerInfo *root,
 						   Expr *clause,
 						   Expr *orclause,
 						   bool is_pushed_down,
-						   bool outerjoin_delayed,
 						   bool pseudoconstant,
 						   Index security_level,
 						   Relids required_relids,
@@ -119,7 +113,6 @@ make_restrictinfo_internal(PlannerInfo *root,
 	restrictinfo->clause = clause;
 	restrictinfo->orclause = orclause;
 	restrictinfo->is_pushed_down = is_pushed_down;
-	restrictinfo->outerjoin_delayed = outerjoin_delayed;
 	restrictinfo->pseudoconstant = pseudoconstant;
 	restrictinfo->has_clone = false;	/* may get set by caller */
 	restrictinfo->is_clone = false; /* may get set by caller */
@@ -251,7 +244,7 @@ make_restrictinfo_internal(PlannerInfo *root,
  * implicit-AND lists at top level of RestrictInfo lists.  Only ORs and
  * simple clauses are valid RestrictInfos.
  *
- * The same is_pushed_down, outerjoin_delayed, and pseudoconstant flag
+ * The same is_pushed_down and pseudoconstant flag
  * values can be applied to all RestrictInfo nodes in the result.  Likewise
  * for security_level and outer_relids.
  *
@@ -263,7 +256,6 @@ static Expr *
 make_sub_restrictinfos(PlannerInfo *root,
 					   Expr *clause,
 					   bool is_pushed_down,
-					   bool outerjoin_delayed,
 					   bool pseudoconstant,
 					   Index security_level,
 					   Relids required_relids,
@@ -279,7 +271,6 @@ make_sub_restrictinfos(PlannerInfo *root,
 							 make_sub_restrictinfos(root,
 													lfirst(temp),
 													is_pushed_down,
-													outerjoin_delayed,
 													pseudoconstant,
 													security_level,
 													NULL,
@@ -288,7 +279,6 @@ make_sub_restrictinfos(PlannerInfo *root,
 												   clause,
 												   make_orclause(orlist),
 												   is_pushed_down,
-												   outerjoin_delayed,
 												   pseudoconstant,
 												   security_level,
 												   required_relids,
@@ -304,7 +294,6 @@ make_sub_restrictinfos(PlannerInfo *root,
 							  make_sub_restrictinfos(root,
 													 lfirst(temp),
 													 is_pushed_down,
-													 outerjoin_delayed,
 													 pseudoconstant,
 													 security_level,
 													 required_relids,
@@ -316,7 +305,6 @@ make_sub_restrictinfos(PlannerInfo *root,
 												   clause,
 												   NULL,
 												   is_pushed_down,
-												   outerjoin_delayed,
 												   pseudoconstant,
 												   security_level,
 												   required_relids,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 6a6fbf3fd4..e759f99161 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -2370,24 +2370,12 @@ typedef struct LimitPath
  * conditions.  Possibly we should rename it to reflect that meaning?  But
  * see also the comments for RINFO_IS_PUSHED_DOWN, below.)
  *
- * RestrictInfo nodes also contain an outerjoin_delayed flag, which is true
- * if the clause's applicability must be delayed due to any outer joins
- * appearing below it (ie, it has to be postponed to some join level higher
- * than the set of relations it actually references).
- *
  * There is also an outer_relids field, which is NULL except for outer join
  * clauses; for those, it is the set of relids on the outer side of the
  * clause's outer join.  (These are rels that the clause cannot be applied to
  * in parameterized scans, since pushing it into the join's outer side would
  * lead to wrong answers.)
  *
- * XXX this comment needs work, if we don't remove it completely:
- * outerjoin_delayed = true is subtly different from nullable_relids != NULL:
- * a clause might reference some nullable rels and yet not be
- * outerjoin_delayed because it also references all the other rels of the
- * outer join(s). A clause that is not outerjoin_delayed can be enforced
- * anywhere it is computable.
- *
  * To handle security-barrier conditions efficiently, we mark RestrictInfo
  * nodes with a security_level field, in which higher values identify clauses
  * coming from less-trusted sources.  The exact semantics are that a clause
@@ -2461,9 +2449,6 @@ typedef struct RestrictInfo
 	/* true if clause was pushed down in level */
 	bool		is_pushed_down;
 
-	/* true if delayed by lower outer join */
-	bool		outerjoin_delayed;
-
 	/* see comment above */
 	bool		can_join pg_node_attr(equal_ignore);
 
diff --git a/src/include/optimizer/placeholder.h b/src/include/optimizer/placeholder.h
index 3fe9b57415..7026d5a104 100644
--- a/src/include/optimizer/placeholder.h
+++ b/src/include/optimizer/placeholder.h
@@ -22,8 +22,6 @@ extern PlaceHolderVar *make_placeholder_expr(PlannerInfo *root, Expr *expr,
 extern PlaceHolderInfo *find_placeholder_info(PlannerInfo *root,
 											  PlaceHolderVar *phv);
 extern void find_placeholders_in_jointree(PlannerInfo *root);
-extern void update_placeholder_eval_levels(PlannerInfo *root,
-										   SpecialJoinInfo *new_sjinfo);
 extern void fix_placeholder_input_needed_levels(PlannerInfo *root);
 extern void add_placeholders_to_base_rels(PlannerInfo *root);
 extern void add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
diff --git a/src/include/optimizer/restrictinfo.h b/src/include/optimizer/restrictinfo.h
index 1f092371ea..9e24f8cd40 100644
--- a/src/include/optimizer/restrictinfo.h
+++ b/src/include/optimizer/restrictinfo.h
@@ -19,12 +19,11 @@
 
 /* Convenience macro for the common case of a valid-everywhere qual */
 #define make_simple_restrictinfo(root, clause)  \
-	make_restrictinfo(root, clause, true, false, false, 0, NULL, NULL)
+	make_restrictinfo(root, clause, true, false, 0, NULL, NULL)
 
 extern RestrictInfo *make_restrictinfo(PlannerInfo *root,
 									   Expr *clause,
 									   bool is_pushed_down,
-									   bool outerjoin_delayed,
 									   bool pseudoconstant,
 									   Index security_level,
 									   Relids required_relids,
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index a5c09cce01..7f52524b97 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -4020,10 +4020,10 @@ explain (costs off)
 select q1, unique2, thousand, hundred
   from int8_tbl a left join tenk1 b on q1 = unique2
   where coalesce(thousand,123) = q1 and q1 = coalesce(hundred,123);
-                                      QUERY PLAN                                      
---------------------------------------------------------------------------------------
+                                                QUERY PLAN                                                
+----------------------------------------------------------------------------------------------------------
  Nested Loop Left Join
-   Filter: ((COALESCE(b.thousand, 123) = a.q1) AND (a.q1 = COALESCE(b.hundred, 123)))
+   Filter: ((COALESCE(b.thousand, 123) = COALESCE(b.hundred, 123)) AND (a.q1 = COALESCE(b.hundred, 123)))
    ->  Seq Scan on int8_tbl a
    ->  Index Scan using tenk1_unique2 on tenk1 b
          Index Cond: (unique2 = a.q1)
