From a3d6ff0731aea4461b641486f645095efedfa91e Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sat, 25 Jul 2026 12:32:46 -0400
Subject: [PATCH v2 3/3] Remove dead code that's no longer needed for join
 removal.

This is mostly a straightforward exercise in deleting unreferenced
functions, but a couple of points are worth noting.

While replace_relid_callback() was still reached in the previous
patch, it did nothing since we no longer invoked it on any trees
containing RangeTblRef or RestrictInfo nodes.  So we can delete it
and make the two remaining users use ChangeVarNodes instead of
ChangeVarNodesExtended.  Then, ChangeVarNodesExtended is unused
and we can remove that too, along with the exposed definition
of ChangeVarNodes_context (basically reverting that area of the
code to what it was in v17).

ec_clear_derived_clauses, innerrel_is_unique_ext, adjust_relid_set are
now static since they're no longer called from outside their modules.

Bug: #19560
Reported-by: Orestis Markou <orestis@orestis.gr>
Author: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/1186816.1784573544@sss.pgh.pa.us
---
 src/backend/optimizer/path/equivclass.c   |   51 +-
 src/backend/optimizer/plan/analyzejoins.c | 1037 +--------------------
 src/backend/optimizer/plan/initsplan.c    |  179 +---
 src/backend/optimizer/util/joininfo.c     |   36 -
 src/backend/optimizer/util/placeholder.c  |   27 -
 src/backend/rewrite/rewriteManip.c        |   65 +-
 src/include/optimizer/joininfo.h          |    3 -
 src/include/optimizer/paths.h             |    2 -
 src/include/optimizer/placeholder.h       |    1 -
 src/include/optimizer/planmain.h          |    8 -
 src/include/rewrite/rewriteManip.h        |   19 -
 src/tools/pgindent/typedefs.list          |    1 -
 12 files changed, 30 insertions(+), 1399 deletions(-)

diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index e3697df51a2..66fe1c24a58 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -89,6 +89,7 @@ static void ec_build_derives_hash(PlannerInfo *root, EquivalenceClass *ec);
 static void ec_add_derived_clauses(EquivalenceClass *ec, List *clauses);
 static void ec_add_derived_clause(EquivalenceClass *ec, RestrictInfo *clause);
 static void ec_add_clause_to_derives_hash(EquivalenceClass *ec, RestrictInfo *rinfo);
+static void ec_clear_derived_clauses(EquivalenceClass *ec);
 static RestrictInfo *ec_search_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
 											  EquivalenceMember *leftem,
 											  EquivalenceMember *rightem,
@@ -1452,8 +1453,7 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 	 * For the moment we force all the Vars to be available at all join nodes
 	 * for this eclass.  Perhaps this could be improved by doing some
 	 * pre-analysis of which members we prefer to join, but it's no worse than
-	 * what happened in the pre-8.3 code.  (Note: rebuild_eclass_attr_needed
-	 * needs to match this code.)
+	 * what happened in the pre-8.3 code.
 	 */
 	foreach(lc, ec->ec_members)
 	{
@@ -2560,51 +2560,6 @@ reconsider_full_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo)
 	return false;				/* failed to make any deduction */
 }
 
-/*
- * rebuild_eclass_attr_needed
- *	  Put back attr_needed bits for Vars/PHVs needed for join eclasses.
- *
- * This is used to rebuild attr_needed/ph_needed sets after removal of a
- * useless outer join.  It should match what
- * generate_base_implied_equalities_no_const did, except that we call
- * add_vars_to_attr_needed not add_vars_to_targetlist.
- */
-void
-rebuild_eclass_attr_needed(PlannerInfo *root)
-{
-	ListCell   *lc;
-
-	foreach(lc, root->eq_classes)
-	{
-		EquivalenceClass *ec = (EquivalenceClass *) lfirst(lc);
-
-		/*
-		 * We don't expect any EC child members to exist at this point. Ensure
-		 * that's the case, otherwise, we might be getting asked to do
-		 * something this function hasn't been coded for.
-		 */
-		Assert(ec->ec_childmembers == NULL);
-
-		/* Need do anything only for a multi-member, no-const EC. */
-		if (list_length(ec->ec_members) > 1 && !ec->ec_has_const)
-		{
-			ListCell   *lc2;
-
-			foreach(lc2, ec->ec_members)
-			{
-				EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2);
-				List	   *vars = pull_var_clause((Node *) cur_em->em_expr,
-												   PVC_RECURSE_AGGREGATES |
-												   PVC_RECURSE_WINDOWFUNCS |
-												   PVC_INCLUDE_PLACEHOLDERS);
-
-				add_vars_to_attr_needed(root, vars, ec->ec_relids);
-				list_free(vars);
-			}
-		}
-	}
-}
-
 /*
  * find_join_domain
  *	  Find the highest JoinDomain enclosed within the given relid set.
@@ -3826,7 +3781,7 @@ ec_add_clause_to_derives_hash(EquivalenceClass *ec, RestrictInfo *rinfo)
  * when thousands of partitions are involved, so we free it as well -- even
  * though we do not typically free lists.
  */
-void
+static void
 ec_clear_derived_clauses(EquivalenceClass *ec)
 {
 	list_free(ec->ec_derives_list);
diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index ef355ec1fe1..a038b898bdb 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -30,11 +30,9 @@
 #include "catalog/pg_class.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/joininfo.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
-#include "optimizer/placeholder.h"
 #include "optimizer/planmain.h"
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
@@ -62,20 +60,6 @@ bool		enable_self_join_elimination;
 
 /* local functions */
 static bool join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo);
-static void remove_leftjoinrel_from_query(PlannerInfo *root, int relid,
-										  SpecialJoinInfo *sjinfo);
-static void remove_rel_from_query(PlannerInfo *root, int relid,
-								  int subst, SpecialJoinInfo *sjinfo,
-								  Relids joinrelids);
-static void remove_rel_from_restrictinfo(RestrictInfo *rinfo,
-										 int relid, int ojrelid);
-static void remove_rel_from_eclass(PlannerInfo *root, EquivalenceClass *ec,
-								   int relid, int ojrelid);
-static void remove_rel_from_restrictinfo_phvs(RestrictInfo *rinfo,
-											  int relid, int ojrelid);
-static Node *remove_rel_from_phvs(Node *node, int relid, int ojrelid);
-static Node *remove_rel_from_phvs_mutator(Node *node, Relids removable);
-static List *remove_rel_from_joinlist(List *joinlist, int relid, int *nremoved);
 static Node *remove_join_from_jointree(Node *jtnode, int ojrelid,
 									   int *nremoved);
 static void remove_rels_from_query_tree(PlannerInfo *root,
@@ -85,6 +69,14 @@ static bool rel_supports_distinctness(PlannerInfo *root, RelOptInfo *rel);
 static bool rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel,
 								List *clause_list, List **extra_clauses);
 static DistinctColInfo *distinct_col_search(int colno, List *distinct_cols);
+static bool innerrel_is_unique_ext(PlannerInfo *root,
+								   Relids joinrelids,
+								   Relids outerrelids,
+								   RelOptInfo *innerrel,
+								   JoinType jointype,
+								   List *restrictlist,
+								   bool force_cache,
+								   List **extra_clauses);
 static bool is_innerrel_unique_for(PlannerInfo *root,
 								   Relids joinrelids,
 								   Relids outerrelids,
@@ -100,8 +92,6 @@ static void fixup_selfjoin_jointree(PlannerInfo *root, Node *jtnode,
 static List *fixup_selfjoin_quals(PlannerInfo *root, List *quals, int relid);
 static Node *replace_selfjoin_qual(Node *qual);
 static int	self_join_candidates_cmp(const void *a, const void *b);
-static bool replace_relid_callback(Node *node,
-								   ChangeVarNodes_context *context);
 
 
 /*
@@ -367,713 +357,6 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo)
 	return false;
 }
 
-/*
- * Remove the target relid and references to the target join from the
- * planner's data structures, having determined that there is no need
- * to include them in the query.
- *
- * We are not terribly thorough here.  We only bother to update parts of
- * the planner's data structures that will actually be consulted later.
- */
-static void
-remove_leftjoinrel_from_query(PlannerInfo *root, int relid,
-							  SpecialJoinInfo *sjinfo)
-{
-	RelOptInfo *rel = find_base_rel(root, relid);
-	int			ojrelid = sjinfo->ojrelid;
-	Relids		joinrelids;
-	Relids		join_plus_commute;
-	List	   *joininfos;
-	ListCell   *l;
-
-	/* Compute the relid set for the join we are considering */
-	joinrelids = bms_union(sjinfo->min_lefthand, sjinfo->min_righthand);
-	Assert(ojrelid != 0);
-	joinrelids = bms_add_member(joinrelids, ojrelid);
-
-	remove_rel_from_query(root, relid, -1, sjinfo, joinrelids);
-
-	/*
-	 * Remove any joinquals referencing the rel from the joininfo lists.
-	 *
-	 * In some cases, a joinqual has to be put back after deleting its
-	 * reference to the target rel.  This can occur for pseudoconstant and
-	 * outerjoin-delayed quals, which can get marked as requiring the rel in
-	 * order to force them to be evaluated at or above the join.  We can't
-	 * just discard them, though.  Only quals that logically belonged to the
-	 * outer join being discarded should be removed from the query.
-	 *
-	 * We might encounter a qual that is a clone of a deletable qual with some
-	 * outer-join relids added (see deconstruct_distribute_oj_quals).  To
-	 * ensure we get rid of such clones as well, add the relids of all OJs
-	 * commutable with this one to the set we test against for
-	 * pushed-down-ness.
-	 */
-	join_plus_commute = bms_union(joinrelids,
-								  sjinfo->commute_above_r);
-	join_plus_commute = bms_add_members(join_plus_commute,
-										sjinfo->commute_below_l);
-
-	/*
-	 * We must make a copy of the rel's old joininfo list before starting the
-	 * loop, because otherwise remove_join_clause_from_rels would destroy the
-	 * list while we're scanning it.
-	 */
-	joininfos = list_copy(rel->joininfo);
-	foreach(l, joininfos)
-	{
-		RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
-
-		remove_join_clause_from_rels(root, rinfo, rinfo->required_relids);
-
-		if (RINFO_IS_PUSHED_DOWN(rinfo, join_plus_commute))
-		{
-			/*
-			 * There might be references to relid or ojrelid in the
-			 * RestrictInfo's relid sets, as a consequence of PHVs having had
-			 * ph_eval_at sets that include those.  We already checked above
-			 * that any such PHV is safe (and updated its ph_eval_at), so we
-			 * can just drop those references.
-			 */
-			remove_rel_from_restrictinfo(rinfo, relid, ojrelid);
-
-			/*
-			 * Cross-check that the clause itself does not reference the
-			 * target rel or join.
-			 */
-#ifdef USE_ASSERT_CHECKING
-			{
-				Relids		clause_varnos = pull_varnos(root,
-														(Node *) rinfo->clause);
-
-				Assert(!bms_is_member(relid, clause_varnos));
-				Assert(!bms_is_member(ojrelid, clause_varnos));
-			}
-#endif
-			/* Now throw it back into the joininfo lists */
-			distribute_restrictinfo_to_rels(root, rinfo);
-		}
-	}
-
-	/*
-	 * There may be references to the rel in root->fkey_list, but if so,
-	 * match_foreign_keys_to_quals() will get rid of them.
-	 */
-
-	/*
-	 * Now remove the rel from the baserel array to prevent it from being
-	 * referenced again.  (We can't do this earlier because
-	 * remove_join_clause_from_rels will touch it.)
-	 */
-	root->simple_rel_array[relid] = NULL;
-	root->simple_rte_array[relid] = NULL;
-
-	/* And nuke the RelOptInfo, just in case there's another access path */
-	pfree(rel);
-
-	/*
-	 * Now repeat construction of attr_needed bits coming from all other
-	 * sources.
-	 */
-	rebuild_placeholder_attr_needed(root);
-	rebuild_joinclause_attr_needed(root);
-	rebuild_eclass_attr_needed(root);
-	rebuild_lateral_attr_needed(root);
-}
-
-/*
- * Remove the target relid and references to the target join from the
- * planner's data structures, having determined that there is no need
- * to include them in the query.  Optionally replace references to the
- * removed relid with subst if this is a self-join removal.
- *
- * This function serves as the common infrastructure for left-join removal
- * and self-join elimination.  It is intentionally scoped to update only the
- * shared planner data structures that are universally affected by relation
- * removal.  Each specific caller remains responsible for updating any
- * remaining data structures required by its unique removal logic.
- *
- * The specific type of removal being performed is dictated by the combination
- * of the sjinfo and subst parameters.  A non-NULL sjinfo indicates left-join
- * removal.  When sjinfo is NULL, a positive subst value indicates self-join
- * elimination (where references are replaced with subst).
- */
-static void
-remove_rel_from_query(PlannerInfo *root, int relid,
-					  int subst, SpecialJoinInfo *sjinfo,
-					  Relids joinrelids)
-{
-	int			ojrelid = sjinfo ? sjinfo->ojrelid : 0;
-	Index		rti;
-	ListCell   *l;
-	bool		is_outer_join = (sjinfo != NULL);
-	bool		is_self_join = (!is_outer_join && subst > 0);
-	Bitmapset  *seen_serials = NULL;
-
-	Assert(is_outer_join || is_self_join);
-	Assert(!is_outer_join || ojrelid > 0);
-	Assert(!is_outer_join || joinrelids != NULL);
-
-	/*
-	 * Update all_baserels and related relid sets.
-	 */
-	root->all_baserels = adjust_relid_set(root->all_baserels, relid, subst);
-	root->all_query_rels = adjust_relid_set(root->all_query_rels, relid, subst);
-
-	if (is_outer_join)
-	{
-		root->outer_join_rels = bms_del_member(root->outer_join_rels, ojrelid);
-		root->all_query_rels = bms_del_member(root->all_query_rels, ojrelid);
-	}
-
-	/*
-	 * Likewise remove references from SpecialJoinInfo data structures.
-	 *
-	 * This is relevant in case the relation we're deleting is part of the
-	 * relid sets of special joins: those sets have to be adjusted.  If we are
-	 * removing an outer join, the RHS of the target outer join will be made
-	 * empty here, but that's OK since the caller will delete that
-	 * SpecialJoinInfo entirely.
-	 */
-	foreach(l, root->join_info_list)
-	{
-		SpecialJoinInfo *sjinf = (SpecialJoinInfo *) lfirst(l);
-
-		/*
-		 * initsplan.c is fairly cavalier about allowing SpecialJoinInfos'
-		 * lefthand/righthand relid sets to be shared with other data
-		 * structures.  Ensure that we don't modify the original relid sets.
-		 * (The commute_xxx sets are always per-SpecialJoinInfo though.)
-		 */
-		sjinf->min_lefthand = bms_copy(sjinf->min_lefthand);
-		sjinf->min_righthand = bms_copy(sjinf->min_righthand);
-		sjinf->syn_lefthand = bms_copy(sjinf->syn_lefthand);
-		sjinf->syn_righthand = bms_copy(sjinf->syn_righthand);
-
-		/* Now adjust relid bit in the sets: */
-		sjinf->min_lefthand = adjust_relid_set(sjinf->min_lefthand, relid, subst);
-		sjinf->min_righthand = adjust_relid_set(sjinf->min_righthand, relid, subst);
-		sjinf->syn_lefthand = adjust_relid_set(sjinf->syn_lefthand, relid, subst);
-		sjinf->syn_righthand = adjust_relid_set(sjinf->syn_righthand, relid, subst);
-
-		if (is_outer_join)
-		{
-			/* Remove ojrelid bit from the sets: */
-			sjinf->min_lefthand = bms_del_member(sjinf->min_lefthand, ojrelid);
-			sjinf->min_righthand = bms_del_member(sjinf->min_righthand, ojrelid);
-			sjinf->syn_lefthand = bms_del_member(sjinf->syn_lefthand, ojrelid);
-			sjinf->syn_righthand = bms_del_member(sjinf->syn_righthand, ojrelid);
-			/* relid cannot appear in these fields, but ojrelid can: */
-			sjinf->commute_above_l = bms_del_member(sjinf->commute_above_l, ojrelid);
-			sjinf->commute_above_r = bms_del_member(sjinf->commute_above_r, ojrelid);
-			sjinf->commute_below_l = bms_del_member(sjinf->commute_below_l, ojrelid);
-			sjinf->commute_below_r = bms_del_member(sjinf->commute_below_r, ojrelid);
-		}
-		else
-		{
-			/*
-			 * For self-join removal, replace relid references in
-			 * semi_rhs_exprs.
-			 */
-			ChangeVarNodesExtended((Node *) sjinf->semi_rhs_exprs, relid, subst,
-								   0, replace_relid_callback);
-		}
-	}
-
-	/*
-	 * Likewise remove references from PlaceHolderVar data structures,
-	 * removing any no-longer-needed placeholders entirely.  We only remove
-	 * PHVs for left-join removal.  With self-join elimination, PHVs already
-	 * get moved to the remaining relation, where they might still be needed.
-	 * It might also happen that we skip the removal of some PHVs that could
-	 * be removed.  However, the overhead of extra PHVs is small compared to
-	 * the complexity of analysis needed to remove them.
-	 *
-	 * Removal is a bit trickier than it might seem: we can remove PHVs that
-	 * are used at the target rel and/or in the join qual, but not those that
-	 * are used at join partner rels or above the join.  It's not that easy to
-	 * distinguish PHVs used at partner rels from those used in the join qual,
-	 * since they will both have ph_needed sets that are subsets of
-	 * joinrelids.  However, a PHV used at a partner rel could not have the
-	 * target rel in ph_eval_at, so we check that while deciding whether to
-	 * remove or just update the PHV.  There is no corresponding test in
-	 * join_is_removable because it doesn't need to distinguish those cases.
-	 */
-	foreach(l, root->placeholder_list)
-	{
-		PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(l);
-
-		Assert(!is_outer_join || !bms_is_member(relid, phinfo->ph_lateral));
-
-		if (is_outer_join &&
-			bms_is_subset(phinfo->ph_needed, joinrelids) &&
-			bms_is_member(relid, phinfo->ph_eval_at) &&
-			!bms_is_member(ojrelid, phinfo->ph_eval_at))
-		{
-			root->placeholder_list = foreach_delete_current(root->placeholder_list,
-															l);
-			root->placeholder_array[phinfo->phid] = NULL;
-		}
-		else
-		{
-			PlaceHolderVar *phv = phinfo->ph_var;
-
-			phinfo->ph_eval_at = adjust_relid_set(phinfo->ph_eval_at, relid, subst);
-			if (is_outer_join)
-				phinfo->ph_eval_at = bms_del_member(phinfo->ph_eval_at, ojrelid);
-			Assert(!bms_is_empty(phinfo->ph_eval_at));	/* checked previously */
-
-			/* Reduce ph_needed to contain only "relation 0"; see below */
-			if (bms_is_member(0, phinfo->ph_needed))
-				phinfo->ph_needed = bms_make_singleton(0);
-			else
-				phinfo->ph_needed = NULL;
-
-			phv->phrels = adjust_relid_set(phv->phrels, relid, subst);
-			if (is_outer_join)
-				phv->phrels = bms_del_member(phv->phrels, ojrelid);
-			Assert(!bms_is_empty(phv->phrels));
-
-			/*
-			 * For self-join removal, update Var nodes within the PHV's
-			 * expression to reference the replacement relid, and adjust
-			 * ph_lateral for the relid substitution.  (For left-join removal,
-			 * we're removing rather than replacing, and any surviving PHV
-			 * shouldn't reference the removed rel in its expression.  Also,
-			 * relid can't appear in ph_lateral for outer joins.)
-			 */
-			if (is_self_join)
-			{
-				ChangeVarNodesExtended((Node *) phv->phexpr, relid, subst, 0,
-									   replace_relid_callback);
-				phinfo->ph_lateral = adjust_relid_set(phinfo->ph_lateral, relid, subst);
-
-				/*
-				 * ph_lateral might contain rels mentioned in ph_eval_at after
-				 * the replacement, remove them.
-				 */
-				phinfo->ph_lateral = bms_difference(phinfo->ph_lateral, phinfo->ph_eval_at);
-				/* ph_lateral might or might not be empty */
-			}
-
-			Assert(phv->phnullingrels == NULL); /* no need to adjust */
-		}
-	}
-
-	/*
-	 * Likewise remove references from EquivalenceClasses.
-	 *
-	 * For self-join removal, the caller has already updated the
-	 * EquivalenceClasses, so we can skip this step.
-	 */
-	if (is_outer_join)
-	{
-		foreach(l, root->eq_classes)
-		{
-			EquivalenceClass *ec = (EquivalenceClass *) lfirst(l);
-
-			remove_rel_from_eclass(root, ec, relid, ojrelid);
-		}
-	}
-
-	/*
-	 * Finally, we must prepare for the caller to recompute per-Var
-	 * attr_needed and per-PlaceHolderVar ph_needed relid sets.  These have to
-	 * be known accurately, else we may fail to remove other now-removable
-	 * joins.  Because the caller removes the join clause(s) associated with
-	 * the removed join, Vars that were formerly needed may no longer be.
-	 *
-	 * The actual reconstruction of these relid sets is performed by the
-	 * specific caller.  Here, we simply clear out the existing attr_needed
-	 * sets (we already did this above for ph_needed) to ensure they are
-	 * rebuilt from scratch.  We can cheat to one small extent: we can avoid
-	 * re-examining the targetlist and HAVING qual by preserving "relation 0"
-	 * bits from the existing relid sets.  This is safe because we'd never
-	 * remove such references.
-	 *
-	 * Additionally, if we are performing self-join elimination, we must
-	 * replace references to the removed relid with subst within the
-	 * lateral_vars lists.
-	 *
-	 * Also, for left-join removal, we strip the removed rel and join from any
-	 * PlaceHolderVar embedded in the surviving rels' restriction clauses and
-	 * join clauses; we needn't bother with the rel being removed, nor when
-	 * the query has no PlaceHolderVars.
-	 */
-	for (rti = 1; rti < root->simple_rel_array_size; rti++)
-	{
-		RelOptInfo *otherrel = root->simple_rel_array[rti];
-		int			attroff;
-
-		/* there may be empty slots corresponding to non-baserel RTEs */
-		if (otherrel == NULL)
-			continue;
-
-		Assert(otherrel->relid == rti); /* sanity check on array */
-
-		for (attroff = otherrel->max_attr - otherrel->min_attr;
-			 attroff >= 0;
-			 attroff--)
-		{
-			if (bms_is_member(0, otherrel->attr_needed[attroff]))
-				otherrel->attr_needed[attroff] = bms_make_singleton(0);
-			else
-				otherrel->attr_needed[attroff] = NULL;
-		}
-
-		if (is_self_join)
-			ChangeVarNodesExtended((Node *) otherrel->lateral_vars, relid,
-								   subst, 0, replace_relid_callback);
-
-		if (is_outer_join && rti != relid && root->glob->lastPHId != 0)
-		{
-			foreach_node(RestrictInfo, rinfo, otherrel->baserestrictinfo)
-				remove_rel_from_restrictinfo_phvs(rinfo, relid, ojrelid);
-
-			/*
-			 * Join clauses need the same treatment, but there's no value in
-			 * processing any join clause more than once.  So it's slightly
-			 * annoying that we have to find them via the per-base-relation
-			 * joininfo lists.  Avoid duplicate processing by tracking the
-			 * rinfo_serial numbers of join clauses we've already seen.  (This
-			 * doesn't work for is_clone clauses, so we must waste effort on
-			 * them.)
-			 */
-			foreach_node(RestrictInfo, rinfo, otherrel->joininfo)
-			{
-				if (!rinfo->is_clone)	/* else serial number is not unique */
-				{
-					if (bms_is_member(rinfo->rinfo_serial, seen_serials))
-						continue;	/* saw it already */
-					seen_serials = bms_add_member(seen_serials,
-												  rinfo->rinfo_serial);
-				}
-				remove_rel_from_restrictinfo_phvs(rinfo, relid, ojrelid);
-			}
-		}
-	}
-}
-
-/*
- * Remove any references to relid or ojrelid from the RestrictInfo.
- *
- * We only bother to clean out bits in the RestrictInfo's various relid sets,
- * not nullingrel bits in contained Vars and PHVs.  (This might have to be
- * improved sometime.)  However, if the RestrictInfo contains an OR clause
- * we have to also clean up the sub-clauses.
- */
-static void
-remove_rel_from_restrictinfo(RestrictInfo *rinfo, int relid, int ojrelid)
-{
-	/*
-	 * initsplan.c is fairly cavalier about allowing RestrictInfos to share
-	 * relid sets with other RestrictInfos, and SpecialJoinInfos too.  Make
-	 * sure this RestrictInfo has its own relid sets before we modify them.
-	 * (In present usage, clause_relids is probably not shared, but
-	 * required_relids could be; let's not assume anything.)
-	 */
-	rinfo->clause_relids = bms_copy(rinfo->clause_relids);
-	rinfo->clause_relids = bms_del_member(rinfo->clause_relids, relid);
-	rinfo->clause_relids = bms_del_member(rinfo->clause_relids, ojrelid);
-	/* Likewise for required_relids */
-	rinfo->required_relids = bms_copy(rinfo->required_relids);
-	rinfo->required_relids = bms_del_member(rinfo->required_relids, relid);
-	rinfo->required_relids = bms_del_member(rinfo->required_relids, ojrelid);
-	/* Likewise for incompatible_relids */
-	rinfo->incompatible_relids = bms_copy(rinfo->incompatible_relids);
-	rinfo->incompatible_relids = bms_del_member(rinfo->incompatible_relids, relid);
-	rinfo->incompatible_relids = bms_del_member(rinfo->incompatible_relids, ojrelid);
-	/* Likewise for outer_relids */
-	rinfo->outer_relids = bms_copy(rinfo->outer_relids);
-	rinfo->outer_relids = bms_del_member(rinfo->outer_relids, relid);
-	rinfo->outer_relids = bms_del_member(rinfo->outer_relids, ojrelid);
-	/* Likewise for left_relids */
-	rinfo->left_relids = bms_copy(rinfo->left_relids);
-	rinfo->left_relids = bms_del_member(rinfo->left_relids, relid);
-	rinfo->left_relids = bms_del_member(rinfo->left_relids, ojrelid);
-	/* Likewise for right_relids */
-	rinfo->right_relids = bms_copy(rinfo->right_relids);
-	rinfo->right_relids = bms_del_member(rinfo->right_relids, relid);
-	rinfo->right_relids = bms_del_member(rinfo->right_relids, ojrelid);
-
-	/* If it's an OR, recurse to clean up sub-clauses */
-	if (restriction_is_or_clause(rinfo))
-	{
-		ListCell   *lc;
-
-		Assert(is_orclause(rinfo->orclause));
-		foreach(lc, ((BoolExpr *) rinfo->orclause)->args)
-		{
-			Node	   *orarg = (Node *) lfirst(lc);
-
-			/* OR arguments should be ANDs or sub-RestrictInfos */
-			if (is_andclause(orarg))
-			{
-				List	   *andargs = ((BoolExpr *) orarg)->args;
-				ListCell   *lc2;
-
-				foreach(lc2, andargs)
-				{
-					RestrictInfo *rinfo2 = lfirst_node(RestrictInfo, lc2);
-
-					remove_rel_from_restrictinfo(rinfo2, relid, ojrelid);
-				}
-			}
-			else
-			{
-				RestrictInfo *rinfo2 = castNode(RestrictInfo, orarg);
-
-				remove_rel_from_restrictinfo(rinfo2, relid, ojrelid);
-			}
-		}
-	}
-}
-
-/*
- * Remove any references to relid or ojrelid from the EquivalenceClass.
- *
- * We fix the EC and EM relid sets to ensure that implied join equalities will
- * be generated at the appropriate join level(s).  We also strip the removed
- * rel from PlaceHolderVars embedded in member expressions; a member's
- * em_relids reflects ph_eval_at rather than the PHV's phrels, so the latter
- * can still mention the removed rel even when em_relids does not.  Like
- * remove_rel_from_restrictinfo, we don't bother with nullingrel bits in
- * contained plain Vars.
- */
-static void
-remove_rel_from_eclass(PlannerInfo *root, EquivalenceClass *ec,
-					   int relid, int ojrelid)
-{
-	ListCell   *lc;
-
-	/*
-	 * Strip the removed rel/join from PlaceHolderVars in member expressions.
-	 * This is needed even when the EC's relids don't mention the removed rel.
-	 * Plain Vars and Consts can't contain a PlaceHolderVar, so skip them.
-	 */
-	if (root->glob->lastPHId != 0)
-	{
-		foreach_node(EquivalenceMember, em, ec->ec_members)
-		{
-			if (!IsA(em->em_expr, Var) && !IsA(em->em_expr, Const))
-				em->em_expr = (Expr *)
-					remove_rel_from_phvs((Node *) em->em_expr, relid, ojrelid);
-		}
-	}
-
-	if (!bms_is_member(relid, ec->ec_relids) &&
-		!bms_is_member(ojrelid, ec->ec_relids))
-		return;
-
-	/* Fix up the EC's overall relids */
-	ec->ec_relids = bms_del_member(ec->ec_relids, relid);
-	ec->ec_relids = bms_del_member(ec->ec_relids, ojrelid);
-
-	/*
-	 * We don't expect any EC child members to exist at this point.  Ensure
-	 * that's the case, otherwise, we might be getting asked to do something
-	 * this function hasn't been coded for.
-	 */
-	Assert(ec->ec_childmembers == NULL);
-
-	/*
-	 * Fix up the member expressions.  Any non-const member that ends with
-	 * empty em_relids must be a Var or PHV of the removed relation.  We don't
-	 * need it anymore, so we can drop it.
-	 */
-	foreach(lc, ec->ec_members)
-	{
-		EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc);
-
-		if (bms_is_member(relid, cur_em->em_relids) ||
-			bms_is_member(ojrelid, cur_em->em_relids))
-		{
-			Assert(!cur_em->em_is_const);
-			/* em_relids is likely to be shared with some RestrictInfo */
-			cur_em->em_relids = bms_copy(cur_em->em_relids);
-			cur_em->em_relids = bms_del_member(cur_em->em_relids, relid);
-			cur_em->em_relids = bms_del_member(cur_em->em_relids, ojrelid);
-			if (bms_is_empty(cur_em->em_relids))
-				ec->ec_members = foreach_delete_current(ec->ec_members, lc);
-		}
-	}
-
-	/* Fix up the source clauses, in case we can re-use them later */
-	foreach(lc, ec->ec_sources)
-	{
-		RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
-
-		remove_rel_from_restrictinfo(rinfo, relid, ojrelid);
-	}
-
-	/*
-	 * Rather than expend code on fixing up any already-derived clauses, just
-	 * drop them.  (At this point, any such clauses would be base restriction
-	 * clauses, which we'd not need anymore anyway.)
-	 */
-	ec_clear_derived_clauses(ec);
-}
-
-/*
- * Remove any references to relid or ojrelid from the PlaceHolderVars embedded
- * in a RestrictInfo's clause.
- *
- * If it's an OR clause, we must also fix up the orclause, which is a parallel
- * representation built from its own sub-RestrictInfos.  We recurse into the
- * sub-clauses for that, mirroring remove_rel_from_restrictinfo.
- */
-static void
-remove_rel_from_restrictinfo_phvs(RestrictInfo *rinfo, int relid, int ojrelid)
-{
-	rinfo->clause = (Expr *)
-		remove_rel_from_phvs((Node *) rinfo->clause, relid, ojrelid);
-
-	/* If it's an OR, recurse to clean up sub-clauses */
-	if (restriction_is_or_clause(rinfo))
-	{
-		ListCell   *lc;
-
-		Assert(is_orclause(rinfo->orclause));
-		foreach(lc, ((BoolExpr *) rinfo->orclause)->args)
-		{
-			Node	   *orarg = (Node *) lfirst(lc);
-
-			/* OR arguments should be ANDs or sub-RestrictInfos */
-			if (is_andclause(orarg))
-			{
-				List	   *andargs = ((BoolExpr *) orarg)->args;
-				ListCell   *lc2;
-
-				foreach(lc2, andargs)
-				{
-					RestrictInfo *rinfo2 = lfirst_node(RestrictInfo, lc2);
-
-					remove_rel_from_restrictinfo_phvs(rinfo2, relid, ojrelid);
-				}
-			}
-			else
-			{
-				RestrictInfo *rinfo2 = castNode(RestrictInfo, orarg);
-
-				remove_rel_from_restrictinfo_phvs(rinfo2, relid, ojrelid);
-			}
-		}
-	}
-}
-
-/*
- * Remove any references to the specified RT index(es) from the phrels (and
- * phnullingrels) of every PlaceHolderVar in the given expression.
- *
- * remove_rel_from_query() fixes up the relid sets of RestrictInfos and
- * EquivalenceMembers, but not the PlaceHolderVars embedded in their
- * expressions.  That's normally fine, but such an expression may later be
- * translated for an appendrel child and have its relids recomputed by
- * pull_varnos().  A leftover removed relid in phrels would then make
- * pull_varnos() reference a nonexistent rel, so we strip it here to match the
- * canonical PlaceHolderVar.
- */
-static Node *
-remove_rel_from_phvs(Node *node, int relid, int ojrelid)
-{
-	Relids		removable = bms_add_member(bms_make_singleton(relid), ojrelid);
-
-	return remove_rel_from_phvs_mutator(node, removable);
-}
-
-static Node *
-remove_rel_from_phvs_mutator(Node *node, Relids removable)
-{
-	if (node == NULL)
-		return NULL;
-	if (IsA(node, PlaceHolderVar))
-	{
-		PlaceHolderVar *phv = (PlaceHolderVar *) node;
-		Relids		newphrels;
-
-		/* Upper-level PlaceHolderVars should be long gone at this point */
-		Assert(phv->phlevelsup == 0);
-
-		/* Copy the PlaceHolderVar and mutate what's below ... */
-		phv = (PlaceHolderVar *)
-			expression_tree_mutator(node,
-									remove_rel_from_phvs_mutator,
-									removable);
-
-		/*
-		 * ... then strip the removed rels from its relid sets.
-		 *
-		 * If stripping would empty phrels, the PHV is evaluated only at the
-		 * removed relation(s); it then belongs to an EquivalenceMember that
-		 * the caller drops immediately afterwards.  Leave such a PHV
-		 * untouched rather than build one with empty phrels, which the rest
-		 * of the planner assumes never occurs.
-		 */
-		newphrels = bms_difference(phv->phrels, removable);
-		if (!bms_is_empty(newphrels))
-		{
-			phv->phrels = newphrels;
-			phv->phnullingrels = bms_difference(phv->phnullingrels,
-												removable);
-		}
-
-		return (Node *) phv;
-	}
-	return expression_tree_mutator(node,
-								   remove_rel_from_phvs_mutator,
-								   removable);
-}
-
-/*
- * Remove any occurrences of the target relid from a joinlist structure.
- *
- * It's easiest to build a whole new list structure, so we handle it that
- * way.  Efficiency is not a big deal here.
- *
- * *nremoved is incremented by the number of occurrences removed (there
- * should be exactly one, but the caller checks that).
- */
-static List *
-remove_rel_from_joinlist(List *joinlist, int relid, int *nremoved)
-{
-	List	   *result = NIL;
-	ListCell   *jl;
-
-	foreach(jl, joinlist)
-	{
-		Node	   *jlnode = (Node *) lfirst(jl);
-
-		if (IsA(jlnode, RangeTblRef))
-		{
-			int			varno = ((RangeTblRef *) jlnode)->rtindex;
-
-			if (varno == relid)
-				(*nremoved)++;
-			else
-				result = lappend(result, jlnode);
-		}
-		else if (IsA(jlnode, List))
-		{
-			/* Recurse to handle subproblem */
-			List	   *sublist;
-
-			sublist = remove_rel_from_joinlist((List *) jlnode,
-											   relid, nremoved);
-			/* Avoid including empty sub-lists in the result */
-			if (sublist)
-				result = lappend(result, sublist);
-		}
-		else
-		{
-			elog(ERROR, "unrecognized joinlist node type: %d",
-				 (int) nodeTag(jlnode));
-		}
-	}
-
-	return result;
-}
-
-
 /*
  * remove_join_from_jointree
  *		Delete the JoinExpr with the given RT index, along with everything
@@ -1731,7 +1014,7 @@ innerrel_is_unique(PlannerInfo *root,
  * A non-NULL extra_clauses indicates that we're checking for self-join and
  * correspondingly dealing with filtered clauses.
  */
-bool
+static bool
 innerrel_is_unique_ext(PlannerInfo *root,
 					   Relids joinrelids,
 					   Relids outerrelids,
@@ -1910,298 +1193,6 @@ is_innerrel_unique_for(PlannerInfo *root,
 	return rel_is_distinct_for(root, innerrel, clause_list, extra_clauses);
 }
 
-/*
- * Update EC members to point to the remaining relation instead of the removed
- * one, removing duplicates.
- *
- * Restriction clauses for base relations are already distributed to
- * the respective baserestrictinfo lists (see
- * generate_implied_equalities_for_column). The above code has already processed
- * this list and updated these clauses to reference the remaining
- * relation, so that we can skip them here based on their relids.
- *
- * Likewise, we have already processed the join clauses that join the
- * removed relation to the remaining one.
- *
- * Finally, there might be join clauses tying the removed relation to
- * some third relation.  We can't just delete the source clauses and
- * regenerate them from the EC because the corresponding equality
- * operators might be missing (see the handling of ec_broken).
- * Therefore, we will update the references in the source clauses.
- *
- * Derived clauses can be generated again, so it is simpler just to
- * delete them.
- */
-static void
-update_eclasses(EquivalenceClass *ec, int from, int to)
-{
-	List	   *new_members = NIL;
-	List	   *new_sources = NIL;
-
-	/*
-	 * We don't expect any EC child members to exist at this point.  Ensure
-	 * that's the case, otherwise, we might be getting asked to do something
-	 * this function hasn't been coded for.
-	 */
-	Assert(ec->ec_childmembers == NULL);
-
-	foreach_node(EquivalenceMember, em, ec->ec_members)
-	{
-		bool		is_redundant = false;
-
-		if (!bms_is_member(from, em->em_relids))
-		{
-			new_members = lappend(new_members, em);
-			continue;
-		}
-
-		em->em_relids = adjust_relid_set(em->em_relids, from, to);
-		em->em_jdomain->jd_relids = adjust_relid_set(em->em_jdomain->jd_relids, from, to);
-
-		/* We only process inner joins */
-		ChangeVarNodesExtended((Node *) em->em_expr, from, to, 0,
-							   replace_relid_callback);
-
-		foreach_node(EquivalenceMember, other, new_members)
-		{
-			if (!equal(em->em_relids, other->em_relids))
-				continue;
-
-			if (equal(em->em_expr, other->em_expr))
-			{
-				is_redundant = true;
-				break;
-			}
-		}
-
-		if (!is_redundant)
-			new_members = lappend(new_members, em);
-	}
-
-	list_free(ec->ec_members);
-	ec->ec_members = new_members;
-
-	ec_clear_derived_clauses(ec);
-
-	/* Update EC source expressions */
-	foreach_node(RestrictInfo, rinfo, ec->ec_sources)
-	{
-		bool		is_redundant = false;
-
-		if (!bms_is_member(from, rinfo->required_relids))
-		{
-			new_sources = lappend(new_sources, rinfo);
-			continue;
-		}
-
-		ChangeVarNodesExtended((Node *) rinfo, from, to, 0,
-							   replace_relid_callback);
-
-		/*
-		 * After switching the clause to the remaining relation, check it for
-		 * redundancy with existing ones. We don't have to check for
-		 * redundancy with derived clauses, because we've just deleted them.
-		 */
-		foreach_node(RestrictInfo, other, new_sources)
-		{
-			if (!equal(rinfo->clause_relids, other->clause_relids))
-				continue;
-
-			if (equal(rinfo->clause, other->clause))
-			{
-				is_redundant = true;
-				break;
-			}
-		}
-
-		if (!is_redundant)
-			new_sources = lappend(new_sources, rinfo);
-	}
-
-	list_free(ec->ec_sources);
-	ec->ec_sources = new_sources;
-	ec->ec_relids = adjust_relid_set(ec->ec_relids, from, to);
-}
-
-/*
- * "Logically" compares two RestrictInfo's ignoring the 'rinfo_serial' field,
- * which makes almost every RestrictInfo unique.  This type of comparison is
- * useful when removing duplicates while moving RestrictInfo's from removed
- * relation to remaining relation during self-join elimination.
- *
- * XXX: In the future, we might remove the 'rinfo_serial' field completely and
- * get rid of this function.
- */
-static bool
-restrict_infos_logically_equal(RestrictInfo *a, RestrictInfo *b)
-{
-	int			saved_rinfo_serial = a->rinfo_serial;
-	bool		result;
-
-	a->rinfo_serial = b->rinfo_serial;
-	result = equal(a, b);
-	a->rinfo_serial = saved_rinfo_serial;
-
-	return result;
-}
-
-/*
- * This function adds all non-redundant clauses to the keeping relation
- * during self-join elimination.  That is a contradictory operation. On the
- * one hand, we reduce the length of the `restrict` lists, which can
- * impact planning or executing time.  Additionally, we improve the
- * accuracy of cardinality estimation.  On the other hand, it is one more
- * place that can make planning time much longer in specific cases.  It
- * would have been better to avoid calling the equal() function here, but
- * it's the only way to detect duplicated inequality expressions.
- *
- * (*keep_rinfo_list) is given by pointer because it might be altered by
- * distribute_restrictinfo_to_rels().
- */
-static void
-add_non_redundant_clauses(PlannerInfo *root,
-						  List *rinfo_candidates,
-						  List **keep_rinfo_list,
-						  Index removed_relid)
-{
-	foreach_node(RestrictInfo, rinfo, rinfo_candidates)
-	{
-		bool		is_redundant = false;
-
-		Assert(!bms_is_member(removed_relid, rinfo->required_relids));
-
-		foreach_node(RestrictInfo, src, (*keep_rinfo_list))
-		{
-			if (!bms_equal(src->clause_relids, rinfo->clause_relids))
-				/* Can't compare trivially different clauses */
-				continue;
-
-			if (src == rinfo ||
-				(rinfo->parent_ec != NULL &&
-				 src->parent_ec == rinfo->parent_ec) ||
-				restrict_infos_logically_equal(rinfo, src))
-			{
-				is_redundant = true;
-				break;
-			}
-		}
-		if (!is_redundant)
-			distribute_restrictinfo_to_rels(root, rinfo);
-	}
-}
-
-/*
- * A custom callback for ChangeVarNodesExtended() providing Self-join
- * elimination (SJE) related functionality
- *
- * SJE needs to skip the RangeTblRef node type.  During SJE's last
- * step, remove_rel_from_joinlist() removes remaining RangeTblRefs
- * with target relid.  If ChangeVarNodes() replaces the target relid
- * before, remove_rel_from_joinlist() would fail to identify the nodes
- * to delete.
- *
- * SJE also needs to change the relids within RestrictInfo's.
- */
-static bool
-replace_relid_callback(Node *node, ChangeVarNodes_context *context)
-{
-	if (IsA(node, RangeTblRef))
-	{
-		return true;
-	}
-	else if (IsA(node, RestrictInfo))
-	{
-		RestrictInfo *rinfo = (RestrictInfo *) node;
-		int			relid = -1;
-		bool		is_req_equal =
-			(rinfo->required_relids == rinfo->clause_relids);
-		bool		clause_relids_is_multiple =
-			(bms_membership(rinfo->clause_relids) == BMS_MULTIPLE);
-
-		/*
-		 * Recurse down into clauses if the target relation is present in
-		 * clause_relids or required_relids.  We must check required_relids
-		 * because the relation not present in clause_relids might still be
-		 * present somewhere in orclause.
-		 */
-		if (bms_is_member(context->rt_index, rinfo->clause_relids) ||
-			bms_is_member(context->rt_index, rinfo->required_relids))
-		{
-			Relids		new_clause_relids;
-
-			ChangeVarNodesWalkExpression((Node *) rinfo->clause, context);
-			ChangeVarNodesWalkExpression((Node *) rinfo->orclause, context);
-
-			new_clause_relids = adjust_relid_set(rinfo->clause_relids,
-												 context->rt_index,
-												 context->new_index);
-
-			/*
-			 * Incrementally adjust num_base_rels based on the change of
-			 * clause_relids, which could contain both base relids and
-			 * outer-join relids.  This operation is legal until we remove
-			 * only baserels.
-			 */
-			rinfo->num_base_rels -= bms_num_members(rinfo->clause_relids) -
-				bms_num_members(new_clause_relids);
-
-			rinfo->clause_relids = new_clause_relids;
-			rinfo->left_relids =
-				adjust_relid_set(rinfo->left_relids, context->rt_index, context->new_index);
-			rinfo->right_relids =
-				adjust_relid_set(rinfo->right_relids, context->rt_index, context->new_index);
-		}
-
-		if (is_req_equal)
-			rinfo->required_relids = rinfo->clause_relids;
-		else
-			rinfo->required_relids =
-				adjust_relid_set(rinfo->required_relids, context->rt_index, context->new_index);
-
-		rinfo->outer_relids =
-			adjust_relid_set(rinfo->outer_relids, context->rt_index, context->new_index);
-		rinfo->incompatible_relids =
-			adjust_relid_set(rinfo->incompatible_relids, context->rt_index, context->new_index);
-
-		if (rinfo->mergeopfamilies &&
-			bms_get_singleton_member(rinfo->clause_relids, &relid) &&
-			clause_relids_is_multiple &&
-			relid == context->new_index && IsA(rinfo->clause, OpExpr))
-		{
-			Expr	   *leftOp;
-			Expr	   *rightOp;
-
-			leftOp = (Expr *) get_leftop(rinfo->clause);
-			rightOp = (Expr *) get_rightop(rinfo->clause);
-
-			/*
-			 * For self-join elimination, changing varnos could transform
-			 * "t1.a = t2.a" into "t1.a = t1.a".  That is always true as long
-			 * as "t1.a" is not null.  We use equal() to check for such a
-			 * case, and then we replace the qual with a check for not null
-			 * (NullTest).
-			 */
-			if (leftOp != NULL && equal(leftOp, rightOp))
-			{
-				NullTest   *ntest = makeNode(NullTest);
-
-				ntest->arg = leftOp;
-				ntest->nulltesttype = IS_NOT_NULL;
-				ntest->argisrow = false;
-				ntest->location = -1;
-				rinfo->clause = (Expr *) ntest;
-				rinfo->mergeopfamilies = NIL;
-				rinfo->left_em = NULL;
-				rinfo->right_em = NULL;
-			}
-			Assert(rinfo->orclause == NULL);
-		}
-		return true;
-	}
-
-	return false;
-}
-
 /*
  * Remove the toRemove relation after we have proven that it participates only
  * in an unneeded unique self-join with toKeep.
@@ -2604,10 +1595,9 @@ split_selfjoin_quals(PlannerInfo *root, List *joinquals, List **selfjoinquals,
 		 * when we have cast of the same var to different (but compatible)
 		 * types.
 		 */
-		ChangeVarNodesExtended(rightexpr,
-							   bms_singleton_member(rinfo->right_relids),
-							   bms_singleton_member(rinfo->left_relids), 0,
-							   replace_relid_callback);
+		ChangeVarNodes(rightexpr,
+					   bms_singleton_member(rinfo->right_relids),
+					   bms_singleton_member(rinfo->left_relids), 0);
 
 		if (equal(leftexpr, rightexpr))
 			sjoinquals = lappend(sjoinquals, rinfo);
@@ -2643,8 +1633,7 @@ match_unique_clauses(PlannerInfo *root, RelOptInfo *outer, List *uclauses,
 			   bms_is_empty(rinfo->right_relids));
 
 		clause = (Expr *) copyObject(rinfo->clause);
-		ChangeVarNodesExtended((Node *) clause, relid, outer->relid, 0,
-							   replace_relid_callback);
+		ChangeVarNodes((Node *) clause, relid, outer->relid, 0);
 
 		iclause = bms_is_empty(rinfo->left_relids) ? get_rightop(clause) :
 			get_leftop(clause);
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 7b6d7249c66..d8a5c242eef 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -295,8 +295,6 @@ build_base_rel_tlists(PlannerInfo *root, List *final_tlist)
  *	  have a single owning relation; we keep their attr_needed info in
  *	  root->placeholder_list instead.  Find or create the associated
  *	  PlaceHolderInfo entry, and update its ph_needed.
- *
- *	  See also add_vars_to_attr_needed.
  */
 void
 add_vars_to_targetlist(PlannerInfo *root, List *vars,
@@ -350,63 +348,6 @@ add_vars_to_targetlist(PlannerInfo *root, List *vars,
 	}
 }
 
-/*
- * add_vars_to_attr_needed
- *	  This does a subset of what add_vars_to_targetlist does: it just
- *	  updates attr_needed for Vars and ph_needed for PlaceHolderVars.
- *	  We assume the Vars are already in their relations' targetlists.
- *
- *	  This is used to rebuild attr_needed/ph_needed sets after removal
- *	  of a useless outer join.  The removed join clause might have been
- *	  the only upper-level use of some other relation's Var, in which
- *	  case we can reduce that Var's attr_needed and thereby possibly
- *	  open the door to further join removals.  But we can't tell that
- *	  without tedious reconstruction of the attr_needed data.
- *
- *	  Note that if a Var's attr_needed is successfully reduced to empty,
- *	  it will still be in the relation's targetlist even though we do
- *	  not really need the scan plan node to emit it.  The extra plan
- *	  inefficiency seems tiny enough to not be worth spending planner
- *	  cycles to get rid of it.
- */
-void
-add_vars_to_attr_needed(PlannerInfo *root, List *vars,
-						Relids where_needed)
-{
-	ListCell   *temp;
-
-	Assert(!bms_is_empty(where_needed));
-
-	foreach(temp, vars)
-	{
-		Node	   *node = (Node *) lfirst(temp);
-
-		if (IsA(node, Var))
-		{
-			Var		   *var = (Var *) node;
-			RelOptInfo *rel = find_base_rel(root, var->varno);
-			int			attno = var->varattno;
-
-			if (bms_is_subset(where_needed, rel->relids))
-				continue;
-			Assert(attno >= rel->min_attr && attno <= rel->max_attr);
-			attno -= rel->min_attr;
-			rel->attr_needed[attno] = bms_add_members(rel->attr_needed[attno],
-													  where_needed);
-		}
-		else if (IsA(node, PlaceHolderVar))
-		{
-			PlaceHolderVar *phv = (PlaceHolderVar *) node;
-			PlaceHolderInfo *phinfo = find_placeholder_info(root, phv);
-
-			phinfo->ph_needed = bms_add_members(phinfo->ph_needed,
-												where_needed);
-		}
-		else
-			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
-	}
-}
-
 /*****************************************************************************
  *
  *	  GROUP BY
@@ -1226,54 +1167,10 @@ extract_lateral_references(PlannerInfo *root, RelOptInfo *brel, Index rtindex)
 	 */
 	add_vars_to_targetlist(root, newvars, where_needed);
 
-	/*
-	 * Remember the lateral references for rebuild_lateral_attr_needed and
-	 * create_lateral_join_info.
-	 */
+	/* Remember the lateral references for create_lateral_join_info */
 	brel->lateral_vars = newvars;
 }
 
-/*
- * rebuild_lateral_attr_needed
- *	  Put back attr_needed bits for Vars/PHVs needed for lateral references.
- *
- * This is used to rebuild attr_needed/ph_needed sets after removal of a
- * useless outer join.  It should match what find_lateral_references did,
- * except that we call add_vars_to_attr_needed not add_vars_to_targetlist.
- */
-void
-rebuild_lateral_attr_needed(PlannerInfo *root)
-{
-	Index		rti;
-
-	/* We need do nothing if the query contains no LATERAL RTEs */
-	if (!root->hasLateralRTEs)
-		return;
-
-	/* Examine the same baserels that find_lateral_references did */
-	for (rti = 1; rti < root->simple_rel_array_size; rti++)
-	{
-		RelOptInfo *brel = root->simple_rel_array[rti];
-		Relids		where_needed;
-
-		if (brel == NULL)
-			continue;
-		if (brel->reloptkind != RELOPT_BASEREL)
-			continue;
-
-		/*
-		 * We don't need to repeat all of extract_lateral_references, since it
-		 * kindly saved the extracted Vars/PHVs in lateral_vars.
-		 */
-		if (brel->lateral_vars == NIL)
-			continue;
-
-		where_needed = bms_make_singleton(rti);
-
-		add_vars_to_attr_needed(root, brel->lateral_vars, where_needed);
-	}
-}
-
 /*
  * create_lateral_join_info
  *	  Fill in the per-base-relation direct_lateral_relids, lateral_relids
@@ -3227,9 +3124,6 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	 * var propagation is ensured by making ojscope include input rels from
 	 * both sides of the join.
 	 *
-	 * See also rebuild_joinclause_attr_needed, which has to partially repeat
-	 * this work after removal of an outer join.
-	 *
 	 * Note: if the clause gets absorbed into an EquivalenceClass then this
 	 * may be unnecessary, but for now we have to do it to cover the case
 	 * where the EC becomes ec_broken and we end up reinserting the original
@@ -3803,11 +3697,6 @@ process_implied_equality(PlannerInfo *root,
 	 * some of the Vars could have missed having that done because they only
 	 * appeared in single-relation clauses originally.  So do it here for
 	 * safety.
-	 *
-	 * See also rebuild_joinclause_attr_needed, which has to partially repeat
-	 * this work after removal of an outer join.  (Since we will put this
-	 * clause into the joininfo lists, that function needn't do any extra work
-	 * to find it.)
 	 */
 	if (bms_membership(relids) == BMS_MULTIPLE)
 	{
@@ -3949,72 +3838,6 @@ get_join_domain_min_rels(PlannerInfo *root, Relids domain_relids)
 }
 
 
-/*
- * rebuild_joinclause_attr_needed
- *	  Put back attr_needed bits for Vars/PHVs needed for join clauses.
- *
- * This is used to rebuild attr_needed/ph_needed sets after removal of a
- * useless outer join.  It should match what distribute_qual_to_rels did,
- * except that we call add_vars_to_attr_needed not add_vars_to_targetlist.
- */
-void
-rebuild_joinclause_attr_needed(PlannerInfo *root)
-{
-	/*
-	 * We must examine all join clauses, but there's no value in processing
-	 * any join clause more than once.  So it's slightly annoying that we have
-	 * to find them via the per-base-relation joininfo lists.  Avoid duplicate
-	 * processing by tracking the rinfo_serial numbers of join clauses we've
-	 * already seen.  (This doesn't work for is_clone clauses, so we must
-	 * waste effort on them.)
-	 */
-	Bitmapset  *seen_serials = NULL;
-	Index		rti;
-
-	/* Scan all baserels for join clauses */
-	for (rti = 1; rti < root->simple_rel_array_size; rti++)
-	{
-		RelOptInfo *brel = root->simple_rel_array[rti];
-		ListCell   *lc;
-
-		if (brel == NULL)
-			continue;
-		if (brel->reloptkind != RELOPT_BASEREL)
-			continue;
-
-		foreach(lc, brel->joininfo)
-		{
-			RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
-			Relids		relids = rinfo->required_relids;
-
-			if (!rinfo->is_clone)	/* else serial number is not unique */
-			{
-				if (bms_is_member(rinfo->rinfo_serial, seen_serials))
-					continue;	/* saw it already */
-				seen_serials = bms_add_member(seen_serials,
-											  rinfo->rinfo_serial);
-			}
-
-			if (bms_membership(relids) == BMS_MULTIPLE)
-			{
-				List	   *vars = pull_var_clause((Node *) rinfo->clause,
-												   PVC_RECURSE_AGGREGATES |
-												   PVC_RECURSE_WINDOWFUNCS |
-												   PVC_INCLUDE_PLACEHOLDERS);
-				Relids		where_needed;
-
-				if (rinfo->is_clone)
-					where_needed = bms_intersect(relids, root->all_baserels);
-				else
-					where_needed = relids;
-				add_vars_to_attr_needed(root, vars, where_needed);
-				list_free(vars);
-			}
-		}
-	}
-}
-
-
 /*
  * match_foreign_keys_to_quals
  *		Match foreign-key constraints to equivalence classes and join quals
diff --git a/src/backend/optimizer/util/joininfo.c b/src/backend/optimizer/util/joininfo.c
index ef2f054c39e..f75e35f22ae 100644
--- a/src/backend/optimizer/util/joininfo.c
+++ b/src/backend/optimizer/util/joininfo.c
@@ -145,39 +145,3 @@ add_join_clause_to_rels(PlannerInfo *root,
 		rel->joininfo = lappend(rel->joininfo, restrictinfo);
 	}
 }
-
-/*
- * remove_join_clause_from_rels
- *	  Delete 'restrictinfo' from all the joininfo lists it is in
- *
- * This reverses the effect of add_join_clause_to_rels.  It's used when we
- * discover that a relation need not be joined at all.
- *
- * 'restrictinfo' describes the join clause
- * 'join_relids' is the set of relations participating in the join clause
- *				 (some of these could be outer joins)
- */
-void
-remove_join_clause_from_rels(PlannerInfo *root,
-							 RestrictInfo *restrictinfo,
-							 Relids join_relids)
-{
-	int			cur_relid;
-
-	cur_relid = -1;
-	while ((cur_relid = bms_next_member(join_relids, cur_relid)) >= 0)
-	{
-		RelOptInfo *rel = find_base_rel_ignore_join(root, cur_relid);
-
-		/* We would only have added the clause to baserels */
-		if (rel == NULL)
-			continue;
-
-		/*
-		 * Remove the restrictinfo from the list.  Pointer comparison is
-		 * sufficient.
-		 */
-		Assert(list_member_ptr(rel->joininfo, restrictinfo));
-		rel->joininfo = list_delete_ptr(rel->joininfo, restrictinfo);
-	}
-}
diff --git a/src/backend/optimizer/util/placeholder.c b/src/backend/optimizer/util/placeholder.c
index dd9b11885af..07171f84f61 100644
--- a/src/backend/optimizer/util/placeholder.c
+++ b/src/backend/optimizer/util/placeholder.c
@@ -316,33 +316,6 @@ fix_placeholder_input_needed_levels(PlannerInfo *root)
 	}
 }
 
-/*
- * rebuild_placeholder_attr_needed
- *	  Put back attr_needed bits for Vars/PHVs needed in PlaceHolderVars.
- *
- * This is used to rebuild attr_needed/ph_needed sets after removal of a
- * useless outer join.  It should match what
- * fix_placeholder_input_needed_levels did, except that we call
- * add_vars_to_attr_needed not add_vars_to_targetlist.
- */
-void
-rebuild_placeholder_attr_needed(PlannerInfo *root)
-{
-	ListCell   *lc;
-
-	foreach(lc, root->placeholder_list)
-	{
-		PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
-		List	   *vars = pull_var_clause((Node *) phinfo->ph_var->phexpr,
-										   PVC_RECURSE_AGGREGATES |
-										   PVC_RECURSE_WINDOWFUNCS |
-										   PVC_INCLUDE_PLACEHOLDERS);
-
-		add_vars_to_attr_needed(root, vars, phinfo->ph_eval_at);
-		list_free(vars);
-	}
-}
-
 /*
  * add_placeholders_to_base_rels
  *		Add any required PlaceHolderVars to base rels' targetlists.
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 9c9d1cad33b..330ec596162 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -64,6 +64,7 @@ static bool contain_windowfuncs_walker(Node *node, void *context);
 static bool locate_windowfunc_walker(Node *node,
 									 locate_windowfunc_context *context);
 static bool checkExprHasSubLink_walker(Node *node, void *context);
+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,
@@ -528,23 +529,27 @@ OffsetVarNodes(Node *node, int offset, int sublevels_up)
  *
  * Find all Var nodes in the given tree belonging to a specific relation
  * (identified by sublevels_up and rt_index), and change their varno fields
- * to 'new_index'.  The varnosyn fields are changed too.  Also, adjust other
- * nodes that contain rangetable indexes, such as RangeTblRef and JoinExpr.
+ * to 'new_index' (see adjust_relid_set for the exact change behavior).
+ * The varnosyn fields are changed too.  Also adjust other nodes that
+ * contain rangetable indexes, such as RangeTblRef and JoinExpr.
  *
  * NOTE: although this has the form of a walker, we cheat and modify the
  * nodes in-place.  The given expression tree should have been copied
  * earlier to ensure that no unwanted side-effects occur!
  */
 
+typedef struct
+{
+	int			rt_index;
+	int			new_index;
+	int			sublevels_up;
+} ChangeVarNodes_context;
+
 static bool
 ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
 {
 	if (node == NULL)
 		return false;
-
-	if (context->callback && context->callback(node, context))
-		return false;
-
 	if (IsA(node, Var))
 	{
 		Var		   *var = (Var *) node;
@@ -649,28 +654,14 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
 	return expression_tree_walker(node, ChangeVarNodes_walker, context);
 }
 
-/*
- * ChangeVarNodesExtended - similar to ChangeVarNodes, but with an additional
- *							'callback' param
- *
- * ChangeVarNodes changes a given node and all of its underlying nodes.  This
- * version of function additionally takes a callback, which has a chance to
- * process a node before ChangeVarNodes_walker.  A callback returns a boolean
- * value indicating if the given node should be skipped from further processing
- * by ChangeVarNodes_walker.  The callback is called only for expressions and
- * other children nodes of a Query processed by a walker.  Initial processing
- * of the root Query node doesn't invoke the callback.
- */
 void
-ChangeVarNodesExtended(Node *node, int rt_index, int new_index,
-					   int sublevels_up, ChangeVarNodes_callback callback)
+ChangeVarNodes(Node *node, int rt_index, int new_index, int sublevels_up)
 {
 	ChangeVarNodes_context context;
 
 	context.rt_index = rt_index;
 	context.new_index = new_index;
 	context.sublevels_up = sublevels_up;
-	context.callback = callback;
 
 	/*
 	 * Must be prepared to start with a Query or a bare expression tree; if
@@ -717,36 +708,6 @@ ChangeVarNodesExtended(Node *node, int rt_index, int new_index,
 		ChangeVarNodes_walker(node, &context);
 }
 
-void
-ChangeVarNodes(Node *node, int rt_index, int new_index, int sublevels_up)
-{
-	ChangeVarNodesExtended(node, rt_index, new_index, sublevels_up, NULL);
-}
-
-/*
- * ChangeVarNodesWalkExpression - process subexpression within a callback
- *								  function passed to ChangeVarNodesExtended.
- *
- * This is intended to be used by a callback that needs to recursively
- * process subexpressions of some node being visited by an outer
- * ChangeVarNodesExtended call, instead of relying on ChangeVarNodes_walker's
- * default recursion.  We invoke ChangeVarNodes_walker directly rather than
- * via expression_tree_walker, because expression_tree_walker only visits
- * child nodes and would fail to process the passed node itself --
- * for example, a bare Var node would not get its varno adjusted.
- *
- * Because this calls ChangeVarNodes_walker directly, if the passed node is
- * a Query, it will be treated as a sub-Query: sublevels_up is incremented
- * before recursing into it, and Query-level fields (resultRelation,
- * mergeTargetRelation, rowMarks, etc.) will not be adjusted.  Do not apply
- * this to a top-level Query node; use ChangeVarNodesExtended for that.
- */
-bool
-ChangeVarNodesWalkExpression(Node *node, ChangeVarNodes_context *context)
-{
-	return ChangeVarNodes_walker(node, context);
-}
-
 /*
  * adjust_relid_set - substitute newrelid for oldrelid in a Relid set
  *
@@ -756,7 +717,7 @@ ChangeVarNodesWalkExpression(Node *node, ChangeVarNodes_context *context)
  * a special varno, this function does nothing.  When newrelid is a special
  * varno, this function behaves as delete.
  */
-Relids
+static Relids
 adjust_relid_set(Relids relids, int oldrelid, int newrelid)
 {
 	if (!IS_SPECIAL_VARNO(oldrelid) && bms_is_member(oldrelid, relids))
diff --git a/src/include/optimizer/joininfo.h b/src/include/optimizer/joininfo.h
index 117195fbcfa..aa26cc670f9 100644
--- a/src/include/optimizer/joininfo.h
+++ b/src/include/optimizer/joininfo.h
@@ -23,8 +23,5 @@ extern bool have_relevant_joinclause(PlannerInfo *root,
 extern void add_join_clause_to_rels(PlannerInfo *root,
 									RestrictInfo *restrictinfo,
 									Relids join_relids);
-extern void remove_join_clause_from_rels(PlannerInfo *root,
-										 RestrictInfo *restrictinfo,
-										 Relids join_relids);
 
 #endif							/* JOININFO_H */
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 17f2099ec3b..051e38c1189 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -137,7 +137,6 @@ extern bool process_equivalence(PlannerInfo *root,
 extern Expr *canonicalize_ec_expression(Expr *expr,
 										Oid req_type, Oid req_collation);
 extern void reconsider_outer_join_clauses(PlannerInfo *root);
-extern void rebuild_eclass_attr_needed(PlannerInfo *root);
 extern EquivalenceClass *get_eclass_for_sort_expr(PlannerInfo *root,
 												  Expr *expr,
 												  List *opfamilies,
@@ -208,7 +207,6 @@ extern bool eclass_useful_for_merging(PlannerInfo *root,
 extern bool is_redundant_derived_clause(RestrictInfo *rinfo, List *clauselist);
 extern bool is_redundant_with_indexclauses(RestrictInfo *rinfo,
 										   List *indexclauses);
-extern void ec_clear_derived_clauses(EquivalenceClass *ec);
 
 /*
  * pathkeys.c
diff --git a/src/include/optimizer/placeholder.h b/src/include/optimizer/placeholder.h
index 60798281090..2099075e670 100644
--- a/src/include/optimizer/placeholder.h
+++ b/src/include/optimizer/placeholder.h
@@ -23,7 +23,6 @@ extern PlaceHolderInfo *find_placeholder_info(PlannerInfo *root,
 											  PlaceHolderVar *phv);
 extern void find_placeholders_in_jointree(PlannerInfo *root);
 extern void fix_placeholder_input_needed_levels(PlannerInfo *root);
-extern void rebuild_placeholder_attr_needed(PlannerInfo *root);
 extern void add_placeholders_to_base_rels(PlannerInfo *root);
 extern void add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
 										RelOptInfo *outer_rel, RelOptInfo *inner_rel,
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 85301d912f0..f7e22f5989d 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -73,12 +73,9 @@ extern void add_other_rels_to_query(PlannerInfo *root);
 extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
 extern void add_vars_to_targetlist(PlannerInfo *root, List *vars,
 								   Relids where_needed);
-extern void add_vars_to_attr_needed(PlannerInfo *root, List *vars,
-									Relids where_needed);
 extern void remove_useless_groupby_columns(PlannerInfo *root);
 extern void setup_eager_aggregation(PlannerInfo *root);
 extern void find_lateral_references(PlannerInfo *root);
-extern void rebuild_lateral_attr_needed(PlannerInfo *root);
 extern void create_lateral_join_info(PlannerInfo *root);
 extern List *deconstruct_jointree(PlannerInfo *root);
 extern bool restriction_is_always_true(PlannerInfo *root,
@@ -102,7 +99,6 @@ extern RestrictInfo *build_implied_join_equality(PlannerInfo *root,
 												 Expr *item2,
 												 Relids qualscope,
 												 Index security_level);
-extern void rebuild_joinclause_attr_needed(PlannerInfo *root);
 extern void match_foreign_keys_to_quals(PlannerInfo *root);
 
 /*
@@ -115,10 +111,6 @@ extern bool query_is_distinct_for(Query *query, List *distinct_cols);
 extern bool innerrel_is_unique(PlannerInfo *root,
 							   Relids joinrelids, Relids outerrelids, RelOptInfo *innerrel,
 							   JoinType jointype, List *restrictlist, bool force_cache);
-extern bool innerrel_is_unique_ext(PlannerInfo *root, Relids joinrelids,
-								   Relids outerrelids, RelOptInfo *innerrel,
-								   JoinType jointype, List *restrictlist,
-								   bool force_cache, List **extra_clauses);
 extern bool remove_useless_self_joins(PlannerInfo *root, List *joinlist);
 
 /*
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index 9d234cb8741..2d15d8f8f95 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -41,30 +41,11 @@ typedef enum ReplaceVarsNoMatchOption
 	REPLACEVARS_SUBSTITUTE_NULL,	/* replace with a NULL Const */
 } ReplaceVarsNoMatchOption;
 
-typedef struct ChangeVarNodes_context ChangeVarNodes_context;
-
-typedef bool (*ChangeVarNodes_callback) (Node *node,
-										 ChangeVarNodes_context *arg);
-
-struct ChangeVarNodes_context
-{
-	int			rt_index;
-	int			new_index;
-	int			sublevels_up;
-	ChangeVarNodes_callback callback;
-};
-
-pg_nodiscard extern Relids adjust_relid_set(Relids relids, int oldrelid, int newrelid);
 extern void CombineRangeTables(List **dst_rtable, List **dst_perminfos,
 							   List *src_rtable, List *src_perminfos);
 extern void OffsetVarNodes(Node *node, int offset, int sublevels_up);
 extern void ChangeVarNodes(Node *node, int rt_index, int new_index,
 						   int sublevels_up);
-extern void ChangeVarNodesExtended(Node *node, int rt_index, int new_index,
-								   int sublevels_up,
-								   ChangeVarNodes_callback callback);
-extern bool ChangeVarNodesWalkExpression(Node *node,
-										 ChangeVarNodes_context *context);
 extern void IncrementVarSublevelsUp(Node *node, int delta_sublevels_up,
 									int min_sublevels_up);
 extern void IncrementVarSublevelsUp_rtable(List *rtable,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 56c1f997f88..41ceb9b160d 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -431,7 +431,6 @@ CatalogId
 CatalogIdMapEntry
 CatalogIndexState
 ChangeContext
-ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
 CheckPoint
-- 
2.52.0

