v28-0001-Speed-up-searches-for-child-EquivalenceMembers.patch

application/octet-stream

Filename: v28-0001-Speed-up-searches-for-child-EquivalenceMembers.patch
Type: application/octet-stream
Part: 1
Message: Re: [PoC] Reducing planning time when tables have many partitions

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch v28-0001
Subject: Speed up searches for child EquivalenceMembers
File+
contrib/postgres_fdw/postgres_fdw.c 29 7
src/backend/optimizer/path/equivclass.c 394 70
src/backend/optimizer/path/indxpath.c 40 7
src/backend/optimizer/path/pathkeys.c 6 3
src/backend/optimizer/plan/createplan.c 35 26
src/backend/optimizer/util/inherit.c 14 0
src/backend/optimizer/util/relnode.c 89 0
src/include/nodes/pathnodes.h 61 0
src/include/optimizer/pathnode.h 18 0
src/include/optimizer/paths.h 11 1
src/tools/pgindent/typedefs.list 1 0
From f4299a4080a29e6ddf91ccb25eb5c96900aaf3c5 Mon Sep 17 00:00:00 2001
From: Yuya Watari <watari.yuya@gmail.com>
Date: Fri, 25 Aug 2023 10:43:36 +0900
Subject: [PATCH v28 1/7] Speed up searches for child EquivalenceMembers

Traditionally, child EquivalenceMembers were in the
EquivalenceClass->ec_members. When we wanted to find some child members
matching a request, we had to perform a linear search. This search
became heavy when tables had many partitions, leading to much planning
time.

After this commit, child EquivalenceMembers no longer exist in
ec_members. Instead, RelOptInfos have them. This change demonstrates a
significant performance improvement in planning time.
---
 contrib/postgres_fdw/postgres_fdw.c     |  36 +-
 src/backend/optimizer/path/equivclass.c | 464 ++++++++++++++++++++----
 src/backend/optimizer/path/indxpath.c   |  47 ++-
 src/backend/optimizer/path/pathkeys.c   |   9 +-
 src/backend/optimizer/plan/createplan.c |  61 ++--
 src/backend/optimizer/util/inherit.c    |  14 +
 src/backend/optimizer/util/relnode.c    |  89 +++++
 src/include/nodes/pathnodes.h           |  61 ++++
 src/include/optimizer/pathnode.h        |  18 +
 src/include/optimizer/paths.h           |  12 +-
 src/tools/pgindent/typedefs.list        |   1 +
 11 files changed, 698 insertions(+), 114 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index c0810fbd7c8..e68c116164c 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -7828,13 +7828,35 @@ conversion_error_callback(void *arg)
 EquivalenceMember *
 find_em_for_rel(PlannerInfo *root, EquivalenceClass *ec, RelOptInfo *rel)
 {
-	ListCell   *lc;
-
 	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private;
+	Relids		top_parent_rel_relids;
+	EquivalenceChildMemberIterator it;
+	EquivalenceMember *em;
+
+	/*
+	 * First, we translate the given Relids to their top-level parents. This
+	 * is required because an EquivalenceClass contains only parent
+	 * EquivalenceMembers, and we have to translate top-level ones to get
+	 * child members. We can skip such translations if we now see top-level
+	 * ones, i.e., when top_parent_rel is NULL. See the
+	 * find_relids_top_parents()'s definition for more details.
+	 */
+	top_parent_rel_relids = find_relids_top_parents(root, rel->relids);
 
-	foreach(lc, ec->ec_members)
+	/*
+	 * If we need to see child EquivalenceMembers, we access them via
+	 * EquivalenceChildMemberIterator during the iteration.
+	 */
+	setup_eclass_child_member_iterator(&it, ec);
+	while ((em = eclass_child_member_iterator_next(&it)))
 	{
-		EquivalenceMember *em = (EquivalenceMember *) lfirst(lc);
+		/*
+		 * If child EquivalenceMembers may match the request, we add and
+		 * iterate over them by calling iterate_child_rel_equivalences().
+		 */
+		if (unlikely(top_parent_rel_relids != NULL) && !em->em_is_child &&
+			bms_is_subset(em->em_relids, top_parent_rel_relids))
+			iterate_child_rel_equivalences(&it, root, ec, em, rel->relids);
 
 		/*
 		 * Note we require !bms_is_empty, else we'd accept constant
@@ -7846,6 +7868,7 @@ find_em_for_rel(PlannerInfo *root, EquivalenceClass *ec, RelOptInfo *rel)
 			is_foreign_expr(root, rel, em->em_expr))
 			return em;
 	}
+	dispose_eclass_child_member_iterator(&it);
 
 	return NULL;
 }
@@ -7899,9 +7922,8 @@ find_em_for_rel_target(PlannerInfo *root, EquivalenceClass *ec,
 			if (em->em_is_const)
 				continue;
 
-			/* Ignore child members */
-			if (em->em_is_child)
-				continue;
+			/* Child members should not exist in ec_members */
+			Assert(!em->em_is_child);
 
 			/* Match if same expression (after stripping relabel) */
 			em_expr = em->em_expr;
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index fae137dd825..25ce4dd2242 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -33,10 +33,14 @@
 #include "utils/lsyscache.h"
 
 
+static EquivalenceMember *make_eq_member(EquivalenceClass *ec,
+										 Expr *expr, Relids relids,
+										 JoinDomain *jdomain,
+										 EquivalenceMember *parent,
+										 Oid datatype);
 static EquivalenceMember *add_eq_member(EquivalenceClass *ec,
 										Expr *expr, Relids relids,
 										JoinDomain *jdomain,
-										EquivalenceMember *parent,
 										Oid datatype);
 static void generate_base_implied_equalities_const(PlannerInfo *root,
 												   EquivalenceClass *ec);
@@ -68,6 +72,11 @@ static bool reconsider_outer_join_clause(PlannerInfo *root,
 static bool reconsider_full_join_clause(PlannerInfo *root,
 										OuterJoinClauseInfo *ojcinfo);
 static JoinDomain *find_join_domain(PlannerInfo *root, Relids relids);
+static void add_transformed_child_version(EquivalenceChildMemberIterator *it,
+										  PlannerInfo *root,
+										  EquivalenceClass *ec,
+										  EquivalenceMember *parent_em,
+										  RelOptInfo *child_rel);
 static Bitmapset *get_eclass_indexes_for_relids(PlannerInfo *root,
 												Relids relids);
 static Bitmapset *get_common_eclass_indexes(PlannerInfo *root, Relids relids1,
@@ -373,7 +382,7 @@ process_equivalence(PlannerInfo *root,
 	{
 		/* Case 3: add item2 to ec1 */
 		em2 = add_eq_member(ec1, item2, item2_relids,
-							jdomain, NULL, item2_type);
+							jdomain, item2_type);
 		ec1->ec_sources = lappend(ec1->ec_sources, restrictinfo);
 		ec1->ec_min_security = Min(ec1->ec_min_security,
 								   restrictinfo->security_level);
@@ -390,7 +399,7 @@ process_equivalence(PlannerInfo *root,
 	{
 		/* Case 3: add item1 to ec2 */
 		em1 = add_eq_member(ec2, item1, item1_relids,
-							jdomain, NULL, item1_type);
+							jdomain, item1_type);
 		ec2->ec_sources = lappend(ec2->ec_sources, restrictinfo);
 		ec2->ec_min_security = Min(ec2->ec_min_security,
 								   restrictinfo->security_level);
@@ -422,9 +431,9 @@ process_equivalence(PlannerInfo *root,
 		ec->ec_max_security = restrictinfo->security_level;
 		ec->ec_merged = NULL;
 		em1 = add_eq_member(ec, item1, item1_relids,
-							jdomain, NULL, item1_type);
+							jdomain, item1_type);
 		em2 = add_eq_member(ec, item2, item2_relids,
-							jdomain, NULL, item2_type);
+							jdomain, item2_type);
 
 		root->eq_classes = lappend(root->eq_classes, ec);
 
@@ -510,11 +519,16 @@ canonicalize_ec_expression(Expr *expr, Oid req_type, Oid req_collation)
 }
 
 /*
- * add_eq_member - build a new EquivalenceMember and add it to an EC
+ * make_eq_member
+ *
+ * Build a new EquivalenceMember without adding it to an EC. If 'parent'
+ * parameter is NULL, the result will be a parent member, otherwise a child
+ * member. Note that child EquivalenceMembers should not be added to its
+ * parent EquivalenceClass.
  */
 static EquivalenceMember *
-add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids,
-			  JoinDomain *jdomain, EquivalenceMember *parent, Oid datatype)
+make_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids,
+			   JoinDomain *jdomain, EquivalenceMember *parent, Oid datatype)
 {
 	EquivalenceMember *em = makeNode(EquivalenceMember);
 
@@ -525,6 +539,8 @@ add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids,
 	em->em_datatype = datatype;
 	em->em_jdomain = jdomain;
 	em->em_parent = parent;
+	em->em_child_relids = NULL;
+	em->em_child_joinrel_relids = NULL;
 
 	if (bms_is_empty(relids))
 	{
@@ -545,11 +561,29 @@ add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids,
 	{
 		ec->ec_relids = bms_add_members(ec->ec_relids, relids);
 	}
-	ec->ec_members = lappend(ec->ec_members, em);
 
 	return em;
 }
 
+/*
+ * add_eq_member - build a new parent EquivalenceMember and add it to an EC
+ *
+ * Note: We don't have a function to add a child member like
+ * add_child_eq_member() because how to do it depends on the relations they are
+ * translated from. See add_child_rel_equivalences() and
+ * add_child_join_rel_equivalences() to see how to add child members.
+ */
+static EquivalenceMember *
+add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids,
+			  JoinDomain *jdomain, Oid datatype)
+{
+	EquivalenceMember *em = make_eq_member(ec, expr, relids, jdomain,
+										   NULL, datatype);
+
+	ec->ec_members = lappend(ec->ec_members, em);
+	return em;
+}
+
 
 /*
  * get_eclass_for_sort_expr
@@ -598,6 +632,17 @@ get_eclass_for_sort_expr(PlannerInfo *root,
 	EquivalenceMember *newem;
 	ListCell   *lc1;
 	MemoryContext oldcontext;
+	Relids		top_parent_rel;
+
+	/*
+	 * First, we translate the given Relids to their top-level parents. This
+	 * is required because an EquivalenceClass contains only parent
+	 * EquivalenceMembers, and we have to translate top-level ones to get
+	 * child members. We can skip such translations if we now see top-level
+	 * ones, i.e., when top_parent_rel is NULL. See the
+	 * find_relids_top_parents()'s definition for more details.
+	 */
+	top_parent_rel = find_relids_top_parents(root, rel);
 
 	/*
 	 * Ensure the expression exposes the correct type and collation.
@@ -616,7 +661,8 @@ get_eclass_for_sort_expr(PlannerInfo *root,
 	foreach(lc1, root->eq_classes)
 	{
 		EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc1);
-		ListCell   *lc2;
+		EquivalenceChildMemberIterator it;
+		EquivalenceMember *cur_em;
 
 		/*
 		 * Never match to a volatile EC, except when we are looking at another
@@ -631,16 +677,28 @@ get_eclass_for_sort_expr(PlannerInfo *root,
 		if (!equal(opfamilies, cur_ec->ec_opfamilies))
 			continue;
 
-		foreach(lc2, cur_ec->ec_members)
+		/*
+		 * If we need to see child EquivalenceMembers, we access them via
+		 * EquivalenceChildMemberIterator during the iteration.
+		 */
+		setup_eclass_child_member_iterator(&it, cur_ec);
+		while ((cur_em = eclass_child_member_iterator_next(&it)) != NULL)
 		{
-			EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2);
+			/*
+			 * If child EquivalenceMembers may match the request, we add and
+			 * iterate over them by calling iterate_child_rel_equivalences().
+			 */
+			if (unlikely(top_parent_rel != NULL) && !cur_em->em_is_child &&
+				bms_equal(cur_em->em_relids, top_parent_rel))
+				iterate_child_rel_equivalences(&it, root, cur_ec, cur_em, rel);
 
 			/*
-			 * Ignore child members unless they match the request.
+			 * Ignore child members unless they match the request. If this
+			 * EquivalenceMember is a child, i.e., translated above, it should
+			 * match the request. We cannot assert this if a request is
+			 * bms_is_subset().
 			 */
-			if (cur_em->em_is_child &&
-				!bms_equal(cur_em->em_relids, rel))
-				continue;
+			Assert(!cur_em->em_is_child || bms_equal(cur_em->em_relids, rel));
 
 			/*
 			 * Match constants only within the same JoinDomain (see
@@ -653,6 +711,7 @@ get_eclass_for_sort_expr(PlannerInfo *root,
 				equal(expr, cur_em->em_expr))
 				return cur_ec;	/* Match! */
 		}
+		dispose_eclass_child_member_iterator(&it);
 	}
 
 	/* No match; does caller want a NULL result? */
@@ -690,7 +749,7 @@ get_eclass_for_sort_expr(PlannerInfo *root,
 	expr_relids = pull_varnos(root, (Node *) expr);
 
 	newem = add_eq_member(newec, copyObject(expr), expr_relids,
-						  jdomain, NULL, opcintype);
+						  jdomain, opcintype);
 
 	/*
 	 * add_eq_member doesn't check for volatile functions, set-returning
@@ -760,19 +819,35 @@ get_eclass_for_sort_expr(PlannerInfo *root,
  * Child EC members are ignored unless they belong to given 'relids'.
  */
 EquivalenceMember *
-find_ec_member_matching_expr(EquivalenceClass *ec,
+find_ec_member_matching_expr(PlannerInfo *root, EquivalenceClass *ec,
 							 Expr *expr,
 							 Relids relids)
 {
-	ListCell   *lc;
+	Relids		top_parent_relids;
+	EquivalenceChildMemberIterator it;
+	EquivalenceMember *em;
+
+	/*
+	 * First, we translate the given Relids to their top-level parents. This
+	 * is required because an EquivalenceClass contains only parent
+	 * EquivalenceMembers, and we have to translate top-level ones to get
+	 * child members. We can skip such translations if we now see top-level
+	 * ones, i.e., when top_parent_rel is NULL. See the
+	 * find_relids_top_parents()'s definition for more details.
+	 */
+	top_parent_relids = find_relids_top_parents(root, relids);
 
 	/* We ignore binary-compatible relabeling on both ends */
 	while (expr && IsA(expr, RelabelType))
 		expr = ((RelabelType *) expr)->arg;
 
-	foreach(lc, ec->ec_members)
+	/*
+	 * If we need to see child EquivalenceMembers, we access them via
+	 * EquivalenceChildMemberIterator during the iteration.
+	 */
+	setup_eclass_child_member_iterator(&it, ec);
+	while ((em = eclass_child_member_iterator_next(&it)) != NULL)
 	{
-		EquivalenceMember *em = (EquivalenceMember *) lfirst(lc);
 		Expr	   *emexpr;
 
 		/*
@@ -782,6 +857,14 @@ find_ec_member_matching_expr(EquivalenceClass *ec,
 		if (em->em_is_const)
 			continue;
 
+		/*
+		 * If child EquivalenceMembers may match the request, we add and
+		 * iterate over them by calling iterate_child_rel_equivalences().
+		 */
+		if (unlikely(top_parent_relids != NULL) && !em->em_is_child &&
+			bms_is_subset(em->em_relids, top_parent_relids))
+			iterate_child_rel_equivalences(&it, root, ec, em, relids);
+
 		/*
 		 * Ignore child members unless they belong to the requested rel.
 		 */
@@ -799,6 +882,7 @@ find_ec_member_matching_expr(EquivalenceClass *ec,
 		if (equal(emexpr, expr))
 			return em;
 	}
+	dispose_eclass_child_member_iterator(&it);
 
 	return NULL;
 }
@@ -841,7 +925,9 @@ find_computable_ec_member(PlannerInfo *root,
 						  bool require_parallel_safe)
 {
 	List	   *exprvars;
-	ListCell   *lc;
+	Relids		top_parent_relids;
+	EquivalenceChildMemberIterator it;
+	EquivalenceMember *em;
 
 	/*
 	 * Pull out the Vars and quasi-Vars present in "exprs".  In the typical
@@ -854,9 +940,23 @@ find_computable_ec_member(PlannerInfo *root,
 							   PVC_INCLUDE_WINDOWFUNCS |
 							   PVC_INCLUDE_PLACEHOLDERS);
 
-	foreach(lc, ec->ec_members)
+	/*
+	 * First, we translate the given Relids to their top-level parents. This
+	 * is required because an EquivalenceClass contains only parent
+	 * EquivalenceMembers, and we have to translate top-level ones to get
+	 * child members. We can skip such translations if we now see top-level
+	 * ones, i.e., when top_parent_rel is NULL. See the
+	 * find_relids_top_parents()'s definition for more details.
+	 */
+	top_parent_relids = find_relids_top_parents(root, relids);
+
+	/*
+	 * If we need to see child EquivalenceMembers, we access them via
+	 * EquivalenceChildMemberIterator during the iteration.
+	 */
+	setup_eclass_child_member_iterator(&it, ec);
+	while ((em = eclass_child_member_iterator_next(&it)) != NULL)
 	{
-		EquivalenceMember *em = (EquivalenceMember *) lfirst(lc);
 		List	   *emvars;
 		ListCell   *lc2;
 
@@ -867,6 +967,14 @@ find_computable_ec_member(PlannerInfo *root,
 		if (em->em_is_const)
 			continue;
 
+		/*
+		 * If child EquivalenceMembers may match the request, we add and
+		 * iterate over them by calling iterate_child_rel_equivalences().
+		 */
+		if (unlikely(top_parent_relids != NULL) && !em->em_is_child &&
+			bms_is_subset(em->em_relids, top_parent_relids))
+			iterate_child_rel_equivalences(&it, root, ec, em, relids);
+
 		/*
 		 * Ignore child members unless they belong to the requested rel.
 		 */
@@ -900,6 +1008,7 @@ find_computable_ec_member(PlannerInfo *root,
 
 		return em;				/* found usable expression */
 	}
+	dispose_eclass_child_member_iterator(&it);
 
 	return NULL;
 }
@@ -938,7 +1047,7 @@ relation_can_be_sorted_early(PlannerInfo *root, RelOptInfo *rel,
 	{
 		Expr	   *targetexpr = (Expr *) lfirst(lc);
 
-		em = find_ec_member_matching_expr(ec, targetexpr, rel->relids);
+		em = find_ec_member_matching_expr(root, ec, targetexpr, rel->relids);
 		if (!em)
 			continue;
 
@@ -1563,7 +1672,19 @@ generate_join_implied_equalities_normal(PlannerInfo *root,
 	List	   *new_members = NIL;
 	List	   *outer_members = NIL;
 	List	   *inner_members = NIL;
-	ListCell   *lc1;
+	Relids		top_parent_join_relids;
+	EquivalenceChildMemberIterator it;
+	EquivalenceMember *cur_em;
+
+	/*
+	 * First, we translate the given Relids to their top-level parents. This
+	 * is required because an EquivalenceClass contains only parent
+	 * EquivalenceMembers, and we have to translate top-level ones to get
+	 * child members. We can skip such translations if we now see top-level
+	 * ones, i.e., when top_parent_rel is NULL. See the
+	 * find_relids_top_parents()'s definition for more details.
+	 */
+	top_parent_join_relids = find_relids_top_parents(root, join_relids);
 
 	/*
 	 * First, scan the EC to identify member values that are computable at the
@@ -1573,10 +1694,20 @@ generate_join_implied_equalities_normal(PlannerInfo *root,
 	 * enforce that any newly computable members are all equal to each other
 	 * as well as to at least one input member, plus enforce at least one
 	 * outer-rel member equal to at least one inner-rel member.
+	 *
+	 * If we need to see child EquivalenceMembers, we access them via
+	 * EquivalenceChildMemberIterator during the iteration.
 	 */
-	foreach(lc1, ec->ec_members)
+	setup_eclass_child_member_iterator(&it, ec);
+	while ((cur_em = eclass_child_member_iterator_next(&it)) != NULL)
 	{
-		EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc1);
+		/*
+		 * If child EquivalenceMembers may match the request, we add and
+		 * iterate over them by calling iterate_child_rel_equivalences().
+		 */
+		if (unlikely(top_parent_join_relids != NULL) && !cur_em->em_is_child &&
+			bms_is_subset(cur_em->em_relids, top_parent_join_relids))
+			iterate_child_rel_equivalences(&it, root, ec, cur_em, join_relids);
 
 		/*
 		 * We don't need to check explicitly for child EC members.  This test
@@ -1593,6 +1724,7 @@ generate_join_implied_equalities_normal(PlannerInfo *root,
 		else
 			new_members = lappend(new_members, cur_em);
 	}
+	dispose_eclass_child_member_iterator(&it);
 
 	/*
 	 * First, select the joinclause if needed.  We can equate any one outer
@@ -1610,6 +1742,7 @@ generate_join_implied_equalities_normal(PlannerInfo *root,
 		Oid			best_eq_op = InvalidOid;
 		int			best_score = -1;
 		RestrictInfo *rinfo;
+		ListCell   *lc1;
 
 		foreach(lc1, outer_members)
 		{
@@ -1684,6 +1817,7 @@ generate_join_implied_equalities_normal(PlannerInfo *root,
 		List	   *old_members = list_concat(outer_members, inner_members);
 		EquivalenceMember *prev_em = NULL;
 		RestrictInfo *rinfo;
+		ListCell   *lc1;
 
 		/* For now, arbitrarily take the first old_member as the one to use */
 		if (old_members)
@@ -1691,7 +1825,7 @@ generate_join_implied_equalities_normal(PlannerInfo *root,
 
 		foreach(lc1, new_members)
 		{
-			EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc1);
+			cur_em = (EquivalenceMember *) lfirst(lc1);
 
 			if (prev_em != NULL)
 			{
@@ -2404,6 +2538,7 @@ reconsider_full_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo)
 		if (matchleft && matchright)
 		{
 			cur_ec->ec_members = list_delete_nth_cell(cur_ec->ec_members, coal_idx);
+			Assert(!bms_is_empty(coal_em->em_relids));
 			return true;
 		}
 
@@ -2525,8 +2660,8 @@ exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2, Oid opfamily)
 		{
 			EquivalenceMember *em = (EquivalenceMember *) lfirst(lc2);
 
-			if (em->em_is_child)
-				continue;		/* ignore children here */
+			/* Child members should not exist in ec_members */
+			Assert(!em->em_is_child);
 			if (equal(item1, em->em_expr))
 				item1member = true;
 			else if (equal(item2, em->em_expr))
@@ -2597,8 +2732,8 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root,
 			EquivalenceMember *em = (EquivalenceMember *) lfirst(lc2);
 			Var		   *var;
 
-			if (em->em_is_child)
-				continue;		/* ignore children here */
+			/* Child members should not exist in ec_members */
+			Assert(!em->em_is_child);
 
 			/* EM must be a Var, possibly with RelabelType */
 			var = (Var *) em->em_expr;
@@ -2695,6 +2830,7 @@ add_child_rel_equivalences(PlannerInfo *root,
 	Relids		top_parent_relids = child_rel->top_parent_relids;
 	Relids		child_relids = child_rel->relids;
 	int			i;
+	ListCell   *lc;
 
 	/*
 	 * EC merging should be complete already, so we can use the parent rel's
@@ -2707,7 +2843,6 @@ add_child_rel_equivalences(PlannerInfo *root,
 	while ((i = bms_next_member(parent_rel->eclass_indexes, i)) >= 0)
 	{
 		EquivalenceClass *cur_ec = (EquivalenceClass *) list_nth(root->eq_classes, i);
-		int			num_members;
 
 		/*
 		 * If this EC contains a volatile expression, then generating child
@@ -2720,15 +2855,9 @@ add_child_rel_equivalences(PlannerInfo *root,
 		/* Sanity check eclass_indexes only contain ECs for parent_rel */
 		Assert(bms_is_subset(top_parent_relids, cur_ec->ec_relids));
 
-		/*
-		 * We don't use foreach() here because there's no point in scanning
-		 * newly-added child members, so we can stop after the last
-		 * pre-existing EC member.
-		 */
-		num_members = list_length(cur_ec->ec_members);
-		for (int pos = 0; pos < num_members; pos++)
+		foreach(lc, cur_ec->ec_members)
 		{
-			EquivalenceMember *cur_em = (EquivalenceMember *) list_nth(cur_ec->ec_members, pos);
+			EquivalenceMember *cur_em = lfirst_node(EquivalenceMember, lc);
 
 			if (cur_em->em_is_const)
 				continue;		/* ignore consts here */
@@ -2741,8 +2870,8 @@ add_child_rel_equivalences(PlannerInfo *root,
 			 * combinations of children.  (But add_child_join_rel_equivalences
 			 * may add targeted combinations for partitionwise-join purposes.)
 			 */
-			if (cur_em->em_is_child)
-				continue;		/* ignore children here */
+			/* Child members should not exist in ec_members */
+			Assert(!cur_em->em_is_child);
 
 			/*
 			 * Consider only members that reference and can be computed at
@@ -2758,6 +2887,7 @@ add_child_rel_equivalences(PlannerInfo *root,
 				/* OK, generate transformed child version */
 				Expr	   *child_expr;
 				Relids		new_relids;
+				EquivalenceMember *child_em;
 
 				if (parent_rel->reloptkind == RELOPT_BASEREL)
 				{
@@ -2787,9 +2917,20 @@ add_child_rel_equivalences(PlannerInfo *root,
 											top_parent_relids);
 				new_relids = bms_add_members(new_relids, child_relids);
 
-				(void) add_eq_member(cur_ec, child_expr, new_relids,
-									 cur_em->em_jdomain,
-									 cur_em, cur_em->em_datatype);
+				child_em = make_eq_member(cur_ec, child_expr, new_relids,
+										  cur_em->em_jdomain,
+										  cur_em, cur_em->em_datatype);
+				child_rel->eclass_child_members = lappend(child_rel->eclass_child_members,
+														  child_em);
+
+				/*
+				 * We save the knowledge that 'child_em' can be translated
+				 * using 'child_rel'. This knowledge is useful for
+				 * add_transformed_child_version() to find child members from
+				 * the given Relids.
+				 */
+				cur_em->em_child_relids = bms_add_member(cur_em->em_child_relids,
+														 child_rel->relid);
 
 				/* Record this EC index for the child rel */
 				child_rel->eclass_indexes = bms_add_member(child_rel->eclass_indexes, i);
@@ -2818,6 +2959,7 @@ add_child_join_rel_equivalences(PlannerInfo *root,
 	Relids		child_relids = child_joinrel->relids;
 	Bitmapset  *matching_ecs;
 	MemoryContext oldcontext;
+	ListCell   *lc;
 	int			i;
 
 	Assert(IS_JOIN_REL(child_joinrel) && IS_JOIN_REL(parent_joinrel));
@@ -2839,7 +2981,6 @@ add_child_join_rel_equivalences(PlannerInfo *root,
 	while ((i = bms_next_member(matching_ecs, i)) >= 0)
 	{
 		EquivalenceClass *cur_ec = (EquivalenceClass *) list_nth(root->eq_classes, i);
-		int			num_members;
 
 		/*
 		 * If this EC contains a volatile expression, then generating child
@@ -2852,15 +2993,9 @@ add_child_join_rel_equivalences(PlannerInfo *root,
 		/* Sanity check on get_eclass_indexes_for_relids result */
 		Assert(bms_overlap(top_parent_relids, cur_ec->ec_relids));
 
-		/*
-		 * We don't use foreach() here because there's no point in scanning
-		 * newly-added child members, so we can stop after the last
-		 * pre-existing EC member.
-		 */
-		num_members = list_length(cur_ec->ec_members);
-		for (int pos = 0; pos < num_members; pos++)
+		foreach(lc, cur_ec->ec_members)
 		{
-			EquivalenceMember *cur_em = (EquivalenceMember *) list_nth(cur_ec->ec_members, pos);
+			EquivalenceMember *cur_em = lfirst_node(EquivalenceMember, lc);
 
 			if (cur_em->em_is_const)
 				continue;		/* ignore consts here */
@@ -2869,8 +3004,8 @@ add_child_join_rel_equivalences(PlannerInfo *root,
 			 * We consider only original EC members here, not
 			 * already-transformed child members.
 			 */
-			if (cur_em->em_is_child)
-				continue;		/* ignore children here */
+			/* Child members should not exist in ec_members */
+			Assert(!cur_em->em_is_child);
 
 			/*
 			 * We may ignore expressions that reference a single baserel,
@@ -2885,6 +3020,7 @@ add_child_join_rel_equivalences(PlannerInfo *root,
 				/* Yes, generate transformed child version */
 				Expr	   *child_expr;
 				Relids		new_relids;
+				EquivalenceMember *child_em;
 
 				if (parent_joinrel->reloptkind == RELOPT_JOINREL)
 				{
@@ -2915,9 +3051,21 @@ add_child_join_rel_equivalences(PlannerInfo *root,
 											top_parent_relids);
 				new_relids = bms_add_members(new_relids, child_relids);
 
-				(void) add_eq_member(cur_ec, child_expr, new_relids,
-									 cur_em->em_jdomain,
-									 cur_em, cur_em->em_datatype);
+				child_em = make_eq_member(cur_ec, child_expr, new_relids,
+										  cur_em->em_jdomain,
+										  cur_em, cur_em->em_datatype);
+				child_joinrel->eclass_child_members =
+					lappend(child_joinrel->eclass_child_members, child_em);
+
+				/*
+				 * We save the knowledge that 'child_em' can be translated
+				 * using 'child_joinrel'. This knowledge is useful for
+				 * add_transformed_child_version() to find child members from
+				 * the given Relids.
+				 */
+				cur_em->em_child_joinrel_relids =
+					bms_add_member(cur_em->em_child_joinrel_relids,
+								   child_joinrel->join_rel_list_index);
 			}
 		}
 	}
@@ -2986,6 +3134,161 @@ add_setop_child_rel_equivalences(PlannerInfo *root, RelOptInfo *child_rel,
 											  list_length(root->eq_classes) - 1);
 }
 
+/*
+ * setup_eclass_child_member_iterator
+ *	  Setup an EquivalenceChildMemberIterator 'it' so that it can iterate over
+ *	  EquivalenceMembers in 'ec'.
+ *
+ * Once used, the caller should dispose of the iterator by calling
+ * dispose_eclass_child_member_iterator().
+ */
+void
+setup_eclass_child_member_iterator(EquivalenceChildMemberIterator *it,
+								   EquivalenceClass *ec)
+{
+	it->index = -1;
+	it->modified = false;
+	it->ec_members = ec->ec_members;
+}
+
+/*
+ * eclass_child_member_iterator_next
+ *	  Get a next EquivalenceMember from an EquivalenceChildMemberIterator 'it'
+ *	  that was setup by setup_eclass_child_member_iterator(). NULL is returned
+ * 	  if there are no members left.
+ */
+EquivalenceMember *
+eclass_child_member_iterator_next(EquivalenceChildMemberIterator *it)
+{
+	if (++it->index < list_length(it->ec_members))
+		return list_nth_node(EquivalenceMember, it->ec_members, it->index);
+	return NULL;
+}
+
+/*
+ * dispose_eclass_child_member_iterator
+ *	  Free any memory allocated by the iterator.
+ */
+void
+dispose_eclass_child_member_iterator(EquivalenceChildMemberIterator *it)
+{
+	/*
+	 * XXX Should we use list_free()? I decided to use this style to take
+	 * advantage of speculative execution.
+	 */
+	if (unlikely(it->modified))
+		pfree(it->ec_members);
+}
+
+/*
+ * add_transformed_child_version
+ *	  Add a transformed EquivalenceMember referencing the given child_rel to
+ *	  the iterator.
+ *
+ * This function is expected to be called only from
+ * iterate_child_rel_equivalences().
+ */
+static void
+add_transformed_child_version(EquivalenceChildMemberIterator *it,
+							  PlannerInfo *root,
+							  EquivalenceClass *ec,
+							  EquivalenceMember *parent_em,
+							  RelOptInfo *child_rel)
+{
+	ListCell   *lc;
+
+	foreach(lc, child_rel->eclass_child_members)
+	{
+		EquivalenceMember *child_em = lfirst_node(EquivalenceMember, lc);
+
+		/* Skip unwanted EquivalenceMembers */
+		if (child_em->em_parent != parent_em)
+			continue;
+
+		/*
+		 * If this is the first time the iterator's list has been modified, we
+		 * need to make a copy of it.
+		 */
+		if (!it->modified)
+		{
+			it->ec_members = list_copy(it->ec_members);
+			it->modified = true;
+		}
+
+		/* Add this child EquivalenceMember to the list */
+		it->ec_members = lappend(it->ec_members, child_em);
+	}
+}
+
+/*
+ * iterate_child_rel_equivalences
+ *	  Add transformed EquivalenceMembers referencing child rels in the given
+ *	  child_relids to the iterator.
+ *
+ * The transformation is done in add_transformed_child_version().
+ */
+void
+iterate_child_rel_equivalences(EquivalenceChildMemberIterator *it,
+							   PlannerInfo *root,
+							   EquivalenceClass *ec,
+							   EquivalenceMember *parent_em,
+							   Relids child_relids)
+{
+	int			i;
+	Relids		matching_relids;
+
+	/* The given EquivalenceMember should be parent */
+	Assert(!parent_em->em_is_child);
+
+	/*
+	 * EquivalenceClass->ec_members has only parent EquivalenceMembers. Child
+	 * members are translated using child RelOptInfos and stored in them. This
+	 * is done in add_child_rel_equivalences() and
+	 * add_child_join_rel_equivalences(). To retrieve child EquivalenceMembers
+	 * of some parent, we need to know which RelOptInfos have such child
+	 * members. This information is stored in indexes named em_child_relids
+	 * and em_child_joinrel_relids.
+	 *
+	 * This function iterates over the child EquivalenceMembers that reference
+	 * the given 'child_relids'. To do this, we first intersect 'child_relids'
+	 * with these indexes. The result contains Relids of RelOptInfos that have
+	 * child EquivalenceMembers we want to retrieve. Then we get the child
+	 * members from the RelOptInfos using add_transformed_child_version().
+	 *
+	 * We need to do these steps for each of the two indexes.
+	 */
+
+	/*
+	 * First, we translate simple rels.
+	 */
+	matching_relids = bms_intersect(parent_em->em_child_relids,
+									child_relids);
+	i = -1;
+	while ((i = bms_next_member(matching_relids, i)) >= 0)
+	{
+		/* Add transformed child version for this child rel */
+		RelOptInfo *child_rel = root->simple_rel_array[i];
+
+		Assert(child_rel != NULL);
+		add_transformed_child_version(it, root, ec, parent_em, child_rel);
+	}
+	bms_free(matching_relids);
+
+	/*
+	 * Next, we try to translate join rels.
+	 */
+	i = -1;
+	while ((i = bms_next_member(parent_em->em_child_joinrel_relids, i)) >= 0)
+	{
+		/* Add transformed child version for this child join rel */
+		RelOptInfo *child_joinrel =
+			list_nth_node(RelOptInfo, root->join_rel_list, i);
+
+		Assert(child_joinrel != NULL);
+		add_transformed_child_version(it, root, ec, parent_em, child_joinrel);
+	}
+}
+
 
 /*
  * generate_implied_equalities_for_column
@@ -3019,7 +3322,8 @@ generate_implied_equalities_for_column(PlannerInfo *root,
 {
 	List	   *result = NIL;
 	bool		is_child_rel = (rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
-	Relids		parent_relids;
+	Relids		parent_relids,
+				top_parent_rel_relids;
 	int			i;
 
 	/* Should be OK to rely on eclass_indexes */
@@ -3028,6 +3332,16 @@ generate_implied_equalities_for_column(PlannerInfo *root,
 	/* Indexes are available only on base or "other" member relations. */
 	Assert(IS_SIMPLE_REL(rel));
 
+	/*
+	 * First, we translate the given Relids to their top-level parents. This
+	 * is required because an EquivalenceClass contains only parent
+	 * EquivalenceMembers, and we have to translate top-level ones to get
+	 * child members. We can skip such translations if we now see top-level
+	 * ones, i.e., when top_parent_rel is NULL. See the
+	 * find_relids_top_parents()'s definition for more details.
+	 */
+	top_parent_rel_relids = find_relids_top_parents(root, rel->relids);
+
 	/* If it's a child rel, we'll need to know what its parent(s) are */
 	if (is_child_rel)
 		parent_relids = find_childrel_parents(root, rel);
@@ -3040,6 +3354,7 @@ generate_implied_equalities_for_column(PlannerInfo *root,
 		EquivalenceClass *cur_ec = (EquivalenceClass *) list_nth(root->eq_classes, i);
 		EquivalenceMember *cur_em;
 		ListCell   *lc2;
+		EquivalenceChildMemberIterator it;
 
 		/* Sanity check eclass_indexes only contain ECs for rel */
 		Assert(is_child_rel || bms_is_subset(rel->relids, cur_ec->ec_relids));
@@ -3060,16 +3375,25 @@ generate_implied_equalities_for_column(PlannerInfo *root,
 		 * column gets matched to.  This is annoying but it only happens in
 		 * corner cases, so for now we live with just reporting the first
 		 * match.  See also get_eclass_for_sort_expr.)
+		 *
+		 * If we need to see child EquivalenceMembers, we access them via
+		 * EquivalenceChildMemberIterator during the iteration.
 		 */
-		cur_em = NULL;
-		foreach(lc2, cur_ec->ec_members)
+		setup_eclass_child_member_iterator(&it, cur_ec);
+		while ((cur_em = eclass_child_member_iterator_next(&it)) != NULL)
 		{
-			cur_em = (EquivalenceMember *) lfirst(lc2);
+			/*
+			 * If child EquivalenceMembers may match the request, we add and
+			 * iterate over them by calling iterate_child_rel_equivalences().
+			 */
+			if (unlikely(top_parent_rel_relids != NULL) && !cur_em->em_is_child &&
+				bms_equal(cur_em->em_relids, top_parent_rel_relids))
+				iterate_child_rel_equivalences(&it, root, cur_ec, cur_em, rel->relids);
 			if (bms_equal(cur_em->em_relids, rel->relids) &&
 				callback(root, rel, cur_ec, cur_em, callback_arg))
 				break;
-			cur_em = NULL;
 		}
+		dispose_eclass_child_member_iterator(&it);
 
 		if (!cur_em)
 			continue;
@@ -3084,8 +3408,8 @@ generate_implied_equalities_for_column(PlannerInfo *root,
 			Oid			eq_op;
 			RestrictInfo *rinfo;
 
-			if (other_em->em_is_child)
-				continue;		/* ignore children here */
+			/* Child members should not exist in ec_members */
+			Assert(!other_em->em_is_child);
 
 			/* Make sure it'll be a join to a different rel */
 			if (other_em == cur_em ||
@@ -3303,8 +3627,8 @@ eclass_useful_for_merging(PlannerInfo *root,
 	{
 		EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc);
 
-		if (cur_em->em_is_child)
-			continue;			/* ignore children here */
+		/* Child members should not exist in ec_members */
+		Assert(!cur_em->em_is_child);
 
 		if (!bms_overlap(cur_em->em_relids, relids))
 			return true;
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 5d102a0d371..4a42752486e 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -190,7 +190,7 @@ static IndexClause *expand_indexqual_rowcompare(PlannerInfo *root,
 												IndexOptInfo *index,
 												Oid expr_op,
 												bool var_on_left);
-static void match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
+static void match_pathkeys_to_index(PlannerInfo *root, IndexOptInfo *index, List *pathkeys,
 									List **orderby_clauses_p,
 									List **clause_columns_p);
 static Expr *match_clause_to_ordering_op(IndexOptInfo *index,
@@ -934,7 +934,7 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 		 * query_pathkeys will allow an incremental sort to be considered on
 		 * the index's partially sorted results.
 		 */
-		match_pathkeys_to_index(index, root->query_pathkeys,
+		match_pathkeys_to_index(root, index, root->query_pathkeys,
 								&orderbyclauses,
 								&orderbyclausecols);
 		if (list_length(root->query_pathkeys) == list_length(orderbyclauses))
@@ -3726,12 +3726,23 @@ expand_indexqual_rowcompare(PlannerInfo *root,
  * item in the given 'pathkeys' list.
  */
 static void
-match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
+match_pathkeys_to_index(PlannerInfo *root, IndexOptInfo *index, List *pathkeys,
 						List **orderby_clauses_p,
 						List **clause_columns_p)
 {
+	Relids		top_parent_index_relids;
 	ListCell   *lc1;
 
+	/*
+	 * First, we translate the given Relids to their top-level parents. This
+	 * is required because an EquivalenceClass contains only parent
+	 * EquivalenceMembers, and we have to translate top-level ones to get
+	 * child members. We can skip such translations if we now see top-level
+	 * ones, i.e., when top_parent_rel is NULL. See the
+	 * find_relids_top_parents()'s definition for more details.
+	 */
+	top_parent_index_relids = find_relids_top_parents(root, index->rel->relids);
+
 	*orderby_clauses_p = NIL;	/* set default results */
 	*clause_columns_p = NIL;
 
@@ -3743,7 +3754,8 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 	{
 		PathKey    *pathkey = (PathKey *) lfirst(lc1);
 		bool		found = false;
-		ListCell   *lc2;
+		EquivalenceChildMemberIterator it;
+		EquivalenceMember *member;
 
 
 		/* Pathkey must request default sort order for the target opfamily */
@@ -3762,16 +3774,36 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 		 * child member of more than one EC.  Therefore, the same index could
 		 * be considered to match more than one pathkey list, which is OK
 		 * here.  See also get_eclass_for_sort_expr.)
+		 *
+		 * If we need to see child EquivalenceMembers, we access them via
+		 * EquivalenceChildMemberIterator during the iteration.
 		 */
-		foreach(lc2, pathkey->pk_eclass->ec_members)
+		setup_eclass_child_member_iterator(&it, pathkey->pk_eclass);
+		while ((member = eclass_child_member_iterator_next(&it)))
 		{
-			EquivalenceMember *member = (EquivalenceMember *) lfirst(lc2);
 			int			indexcol;
 
+			/*
+			 * If child EquivalenceMembers may match the request, we add and
+			 * iterate over them by calling iterate_child_rel_equivalences().
+			 */
+			if (unlikely(top_parent_index_relids != NULL) && !member->em_is_child &&
+				bms_equal(member->em_relids, top_parent_index_relids))
+				iterate_child_rel_equivalences(&it, root, pathkey->pk_eclass, member,
+											   index->rel->relids);
+
 			/* No possibility of match if it references other relations */
-			if (!bms_equal(member->em_relids, index->rel->relids))
+			if (!member->em_is_child &&
+				!bms_equal(member->em_relids, index->rel->relids))
 				continue;
 
+			/*
+			 * If this EquivalenceMember is a child, i.e., translated above,
+			 * it should match the request. We cannot assert this if a request
+			 * is bms_is_subset().
+			 */
+			Assert(bms_equal(member->em_relids, index->rel->relids));
+
 			/*
 			 * We allow any column of the index to match each pathkey; they
 			 * don't have to match left-to-right as you might expect.  This is
@@ -3800,6 +3832,7 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 			if (found)			/* don't want to look at remaining members */
 				break;
 		}
+		dispose_eclass_child_member_iterator(&it);
 
 		/*
 		 * Return the matches found so far when this pathkey couldn't be
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index 03c2ac36e05..b8f9c5f0e93 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -1151,8 +1151,8 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
 				Oid			sub_expr_coll = sub_eclass->ec_collation;
 				ListCell   *k;
 
-				if (sub_member->em_is_child)
-					continue;	/* ignore children here */
+				/* Child members should not exist in ec_members */
+				Assert(!sub_member->em_is_child);
 
 				foreach(k, subquery_tlist)
 				{
@@ -1709,8 +1709,11 @@ select_outer_pathkeys_for_merge(PlannerInfo *root,
 		{
 			EquivalenceMember *em = (EquivalenceMember *) lfirst(lc2);
 
+			/* Child members should not exist in ec_members */
+			Assert(!em->em_is_child);
+
 			/* Potential future join partner? */
-			if (!em->em_is_const && !em->em_is_child &&
+			if (!em->em_is_const &&
 				!bms_overlap(em->em_relids, joinrel->relids))
 				score++;
 		}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 178c572b021..b5f8d5fad0a 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -264,7 +264,9 @@ static IncrementalSort *make_incrementalsort(Plan *lefttree,
 											 int numCols, int nPresortedCols,
 											 AttrNumber *sortColIdx, Oid *sortOperators,
 											 Oid *collations, bool *nullsFirst);
-static Plan *prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
+static Plan *prepare_sort_from_pathkeys(PlannerInfo *root,
+										Plan *lefttree,
+										List *pathkeys,
 										Relids relids,
 										const AttrNumber *reqColIdx,
 										bool adjust_tlist_in_place,
@@ -273,9 +275,11 @@ static Plan *prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
 										Oid **p_sortOperators,
 										Oid **p_collations,
 										bool **p_nullsFirst);
-static Sort *make_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
+static Sort *make_sort_from_pathkeys(PlannerInfo *root,
+									 Plan *lefttree,
+									 List *pathkeys,
 									 Relids relids);
-static IncrementalSort *make_incrementalsort_from_pathkeys(Plan *lefttree,
+static IncrementalSort *make_incrementalsort_from_pathkeys(PlannerInfo *root, Plan *lefttree,
 														   List *pathkeys, Relids relids, int nPresortedCols);
 static Sort *make_sort_from_groupcols(List *groupcls,
 									  AttrNumber *grpColIdx,
@@ -297,7 +301,7 @@ static Group *make_group(List *tlist, List *qual, int numGroupCols,
 						 AttrNumber *grpColIdx, Oid *grpOperators, Oid *grpCollations,
 						 Plan *lefttree);
 static Unique *make_unique_from_sortclauses(Plan *lefttree, List *distinctList);
-static Unique *make_unique_from_pathkeys(Plan *lefttree,
+static Unique *make_unique_from_pathkeys(PlannerInfo *root, Plan *lefttree,
 										 List *pathkeys, int numCols);
 static Gather *make_gather(List *qptlist, List *qpqual,
 						   int nworkers, int rescan_param, bool single_copy, Plan *subplan);
@@ -1285,7 +1289,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
 		 * function result; it must be the same plan node.  However, we then
 		 * need to detect whether any tlist entries were added.
 		 */
-		(void) prepare_sort_from_pathkeys((Plan *) plan, pathkeys,
+		(void) prepare_sort_from_pathkeys(root, (Plan *) plan, pathkeys,
 										  best_path->path.parent->relids,
 										  NULL,
 										  true,
@@ -1329,7 +1333,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
 			 * don't need an explicit sort, to make sure they are returning
 			 * the same sort key columns the Append expects.
 			 */
-			subplan = prepare_sort_from_pathkeys(subplan, pathkeys,
+			subplan = prepare_sort_from_pathkeys(root, subplan, pathkeys,
 												 subpath->parent->relids,
 												 nodeSortColIdx,
 												 false,
@@ -1470,7 +1474,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
 	 * function result; it must be the same plan node.  However, we then need
 	 * to detect whether any tlist entries were added.
 	 */
-	(void) prepare_sort_from_pathkeys(plan, pathkeys,
+	(void) prepare_sort_from_pathkeys(root, plan, pathkeys,
 									  best_path->path.parent->relids,
 									  NULL,
 									  true,
@@ -1501,7 +1505,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
 		subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST);
 
 		/* Compute sort column info, and adjust subplan's tlist as needed */
-		subplan = prepare_sort_from_pathkeys(subplan, pathkeys,
+		subplan = prepare_sort_from_pathkeys(root, subplan, pathkeys,
 											 subpath->parent->relids,
 											 node->sortColIdx,
 											 false,
@@ -1981,7 +1985,7 @@ create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path)
 	Assert(pathkeys != NIL);
 
 	/* Compute sort column info, and adjust subplan's tlist as needed */
-	subplan = prepare_sort_from_pathkeys(subplan, pathkeys,
+	subplan = prepare_sort_from_pathkeys(root, subplan, pathkeys,
 										 best_path->subpath->parent->relids,
 										 gm_plan->sortColIdx,
 										 false,
@@ -2195,7 +2199,7 @@ create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags)
 	 * relids. Thus, if this sort path is based on a child relation, we must
 	 * pass its relids.
 	 */
-	plan = make_sort_from_pathkeys(subplan, best_path->path.pathkeys,
+	plan = make_sort_from_pathkeys(root, subplan, best_path->path.pathkeys,
 								   IS_OTHER_REL(best_path->subpath->parent) ?
 								   best_path->path.parent->relids : NULL);
 
@@ -2219,7 +2223,7 @@ create_incrementalsort_plan(PlannerInfo *root, IncrementalSortPath *best_path,
 	/* See comments in create_sort_plan() above */
 	subplan = create_plan_recurse(root, best_path->spath.subpath,
 								  flags | CP_SMALL_TLIST);
-	plan = make_incrementalsort_from_pathkeys(subplan,
+	plan = make_incrementalsort_from_pathkeys(root, subplan,
 											  best_path->spath.path.pathkeys,
 											  IS_OTHER_REL(best_path->spath.subpath->parent) ?
 											  best_path->spath.path.parent->relids : NULL,
@@ -2288,7 +2292,7 @@ create_upper_unique_plan(PlannerInfo *root, UpperUniquePath *best_path, int flag
 	subplan = create_plan_recurse(root, best_path->subpath,
 								  flags | CP_LABEL_TLIST);
 
-	plan = make_unique_from_pathkeys(subplan,
+	plan = make_unique_from_pathkeys(root, subplan,
 									 best_path->path.pathkeys,
 									 best_path->numkeys);
 
@@ -4550,7 +4554,8 @@ create_mergejoin_plan(PlannerInfo *root,
 		if (!use_incremental_sort)
 		{
 			sort_plan = (Plan *)
-				make_sort_from_pathkeys(outer_plan,
+				make_sort_from_pathkeys(root,
+										outer_plan,
 										best_path->outersortkeys,
 										outer_relids);
 
@@ -4559,7 +4564,8 @@ create_mergejoin_plan(PlannerInfo *root,
 		else
 		{
 			sort_plan = (Plan *)
-				make_incrementalsort_from_pathkeys(outer_plan,
+				make_incrementalsort_from_pathkeys(root,
+												   outer_plan,
 												   best_path->outersortkeys,
 												   outer_relids,
 												   presorted_keys);
@@ -4584,7 +4590,7 @@ create_mergejoin_plan(PlannerInfo *root,
 		 */
 
 		Relids		inner_relids = inner_path->parent->relids;
-		Sort	   *sort = make_sort_from_pathkeys(inner_plan,
+		Sort	   *sort = make_sort_from_pathkeys(root, inner_plan,
 												   best_path->innersortkeys,
 												   inner_relids);
 
@@ -6234,7 +6240,7 @@ make_incrementalsort(Plan *lefttree, int numCols, int nPresortedCols,
  * or a Result stacked atop lefttree).
  */
 static Plan *
-prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
+prepare_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys,
 						   Relids relids,
 						   const AttrNumber *reqColIdx,
 						   bool adjust_tlist_in_place,
@@ -6301,7 +6307,7 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
 			tle = get_tle_by_resno(tlist, reqColIdx[numsortkeys]);
 			if (tle)
 			{
-				em = find_ec_member_matching_expr(ec, tle->expr, relids);
+				em = find_ec_member_matching_expr(root, ec, tle->expr, relids);
 				if (em)
 				{
 					/* found expr at right place in tlist */
@@ -6329,7 +6335,7 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
 			foreach(j, tlist)
 			{
 				tle = (TargetEntry *) lfirst(j);
-				em = find_ec_member_matching_expr(ec, tle->expr, relids);
+				em = find_ec_member_matching_expr(root, ec, tle->expr, relids);
 				if (em)
 				{
 					/* found expr already in tlist */
@@ -6345,7 +6351,7 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
 			/*
 			 * No matching tlist item; look for a computable expression.
 			 */
-			em = find_computable_ec_member(NULL, ec, tlist, relids, false);
+			em = find_computable_ec_member(root, ec, tlist, relids, false);
 			if (!em)
 				elog(ERROR, "could not find pathkey item to sort");
 			pk_datatype = em->em_datatype;
@@ -6416,7 +6422,8 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
  *	  'relids' is the set of relations required by prepare_sort_from_pathkeys()
  */
 static Sort *
-make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids)
+make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys,
+						Relids relids)
 {
 	int			numsortkeys;
 	AttrNumber *sortColIdx;
@@ -6425,7 +6432,7 @@ make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids)
 	bool	   *nullsFirst;
 
 	/* Compute sort column info, and adjust lefttree as needed */
-	lefttree = prepare_sort_from_pathkeys(lefttree, pathkeys,
+	lefttree = prepare_sort_from_pathkeys(root, lefttree, pathkeys,
 										  relids,
 										  NULL,
 										  false,
@@ -6451,8 +6458,9 @@ make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids)
  *	  'nPresortedCols' is the number of presorted columns in input tuples
  */
 static IncrementalSort *
-make_incrementalsort_from_pathkeys(Plan *lefttree, List *pathkeys,
-								   Relids relids, int nPresortedCols)
+make_incrementalsort_from_pathkeys(PlannerInfo *root, Plan *lefttree,
+								   List *pathkeys, Relids relids,
+								   int nPresortedCols)
 {
 	int			numsortkeys;
 	AttrNumber *sortColIdx;
@@ -6461,7 +6469,7 @@ make_incrementalsort_from_pathkeys(Plan *lefttree, List *pathkeys,
 	bool	   *nullsFirst;
 
 	/* Compute sort column info, and adjust lefttree as needed */
-	lefttree = prepare_sort_from_pathkeys(lefttree, pathkeys,
+	lefttree = prepare_sort_from_pathkeys(root, lefttree, pathkeys,
 										  relids,
 										  NULL,
 										  false,
@@ -6820,7 +6828,8 @@ make_unique_from_sortclauses(Plan *lefttree, List *distinctList)
  * as above, but use pathkeys to identify the sort columns and semantics
  */
 static Unique *
-make_unique_from_pathkeys(Plan *lefttree, List *pathkeys, int numCols)
+make_unique_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys,
+						  int numCols)
 {
 	Unique	   *node = makeNode(Unique);
 	Plan	   *plan = &node->plan;
@@ -6883,7 +6892,7 @@ make_unique_from_pathkeys(Plan *lefttree, List *pathkeys, int numCols)
 			foreach(j, plan->targetlist)
 			{
 				tle = (TargetEntry *) lfirst(j);
-				em = find_ec_member_matching_expr(ec, tle->expr, NULL);
+				em = find_ec_member_matching_expr(root, ec, tle->expr, NULL);
 				if (em)
 				{
 					/* found expr already in tlist */
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index c5b906a9d43..3c2198ea5bd 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -470,6 +470,7 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 		RelationGetRelid(parentrel);
 	Oid			childOID = RelationGetRelid(childrel);
 	RangeTblEntry *childrte;
+	Index		topParentRTindex;
 	Index		childRTindex;
 	AppendRelInfo *appinfo;
 	TupleDesc	child_tupdesc;
@@ -577,6 +578,19 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 	Assert(root->append_rel_array[childRTindex] == NULL);
 	root->append_rel_array[childRTindex] = appinfo;
 
+	/*
+	 * Find a top parent rel's index and save it to top_parent_relid_array.
+	 */
+	if (root->top_parent_relid_array == NULL)
+		root->top_parent_relid_array =
+			(Index *) palloc0(root->simple_rel_array_size * sizeof(Index));
+	Assert(root->top_parent_relid_array[childRTindex] == 0);
+	topParentRTindex = parentRTindex;
+	while (root->append_rel_array[topParentRTindex] != NULL &&
+		   root->append_rel_array[topParentRTindex]->parent_relid != 0)
+		topParentRTindex = root->append_rel_array[topParentRTindex]->parent_relid;
+	root->top_parent_relid_array[childRTindex] = topParentRTindex;
+
 	/*
 	 * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
 	 */
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index f96573eb5d6..ce494364173 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -123,11 +123,14 @@ setup_simple_rel_arrays(PlannerInfo *root)
 	if (root->append_rel_list == NIL)
 	{
 		root->append_rel_array = NULL;
+		root->top_parent_relid_array = NULL;
 		return;
 	}
 
 	root->append_rel_array = (AppendRelInfo **)
 		palloc0(size * sizeof(AppendRelInfo *));
+	root->top_parent_relid_array = (Index *)
+		palloc0(size * sizeof(Index));
 
 	/*
 	 * append_rel_array is filled with any already-existing AppendRelInfos,
@@ -148,6 +151,27 @@ setup_simple_rel_arrays(PlannerInfo *root)
 
 		root->append_rel_array[child_relid] = appinfo;
 	}
+
+	/*
+	 * Find a top parent rel's relid for each AppendRelInfo. We cannot do this
+	 * in the last foreach loop because there may be multi-level parent-child
+	 * relations.
+	 */
+	for (int i = 0; i < size; i++)
+	{
+		int			top_parent_relid;
+
+		if (root->append_rel_array[i] == NULL)
+			continue;
+
+		top_parent_relid = root->append_rel_array[i]->parent_relid;
+
+		while (root->append_rel_array[top_parent_relid] != NULL &&
+			   root->append_rel_array[top_parent_relid]->parent_relid != 0)
+			top_parent_relid = root->append_rel_array[top_parent_relid]->parent_relid;
+
+		root->top_parent_relid_array[i] = top_parent_relid;
+	}
 }
 
 /*
@@ -175,12 +199,25 @@ expand_planner_arrays(PlannerInfo *root, int add_size)
 		repalloc0_array(root->simple_rte_array, RangeTblEntry *, root->simple_rel_array_size, new_size);
 
 	if (root->append_rel_array)
+	{
 		root->append_rel_array =
 			repalloc0_array(root->append_rel_array, AppendRelInfo *, root->simple_rel_array_size, new_size);
+		root->top_parent_relid_array =
+			repalloc0_array(root->top_parent_relid_array, Index, root->simple_rel_array_size, new_size);
+	}
 	else
+	{
 		root->append_rel_array =
 			palloc0_array(AppendRelInfo *, new_size);
 
+		/*
+		 * We do not allocate top_parent_relid_array here so that
+		 * find_relids_top_parents() can early find all of the given Relids
+		 * are top-level.
+		 */
+		root->top_parent_relid_array = NULL;
+	}
+
 	root->simple_rel_array_size = new_size;
 }
 
@@ -234,6 +271,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->subplan_params = NIL;
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->amflags = 0;
+	rel->eclass_child_members = NIL;
 	rel->serverid = InvalidOid;
 	if (rte->rtekind == RTE_RELATION)
 	{
@@ -629,6 +667,12 @@ add_join_rel(PlannerInfo *root, RelOptInfo *joinrel)
 	/* GEQO requires us to append the new joinrel to the end of the list! */
 	root->join_rel_list = lappend(root->join_rel_list, joinrel);
 
+	/*
+	 * Store the index of this joinrel to use in
+	 * add_child_join_rel_equivalences().
+	 */
+	joinrel->join_rel_list_index = list_length(root->join_rel_list) - 1;
+
 	/* store it into the auxiliary hashtable if there is one. */
 	if (root->join_rel_hash)
 	{
@@ -741,6 +785,7 @@ build_join_rel(PlannerInfo *root,
 	joinrel->subplan_params = NIL;
 	joinrel->rel_parallel_workers = -1;
 	joinrel->amflags = 0;
+	joinrel->eclass_child_members = NIL;
 	joinrel->serverid = InvalidOid;
 	joinrel->userid = InvalidOid;
 	joinrel->useridiscurrent = false;
@@ -928,6 +973,7 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
 	joinrel->subroot = NULL;
 	joinrel->subplan_params = NIL;
 	joinrel->amflags = 0;
+	joinrel->eclass_child_members = NIL;
 	joinrel->serverid = InvalidOid;
 	joinrel->userid = InvalidOid;
 	joinrel->useridiscurrent = false;
@@ -1490,6 +1536,7 @@ fetch_upper_rel(PlannerInfo *root, UpperRelationKind kind, Relids relids)
 	upperrel->cheapest_total_path = NULL;
 	upperrel->cheapest_unique_path = NULL;
 	upperrel->cheapest_parameterized_paths = NIL;
+	upperrel->eclass_child_members = NIL;	/* XXX Is this required? */
 
 	root->upper_rels[kind] = lappend(root->upper_rels[kind], upperrel);
 
@@ -1530,6 +1577,48 @@ find_childrel_parents(PlannerInfo *root, RelOptInfo *rel)
 }
 
 
+/*
+ * find_relids_top_parents_slow
+ *	  Slow path of find_relids_top_parents() macro.
+ *
+ * Do not call this directly; use the macro instead. See the macro comment for
+ * more details.
+ */
+Relids
+find_relids_top_parents_slow(PlannerInfo *root, Relids relids)
+{
+	Index	   *top_parent_relid_array = root->top_parent_relid_array;
+	Relids		result;
+	bool		is_top_parent;
+	int			i;
+
+	/* This function should be called in the slow path */
+	Assert(top_parent_relid_array != NULL);
+
+	result = NULL;
+	is_top_parent = true;
+	i = -1;
+	while ((i = bms_next_member(relids, i)) >= 0)
+	{
+		int			top_parent_relid = (int) top_parent_relid_array[i];
+
+		if (top_parent_relid == 0)
+			top_parent_relid = i;	/* 'i' has no parents, so add itself */
+		else if (top_parent_relid != i)
+			is_top_parent = false;
+		result = bms_add_member(result, top_parent_relid);
+	}
+
+	if (is_top_parent)
+	{
+		bms_free(result);
+		return NULL;
+	}
+
+	return result;
+}
+
+
 /*
  * get_baserel_parampathinfo
  *		Get the ParamPathInfo for a parameterized path for a base relation,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index add0f9e45fc..a8cc7b19ff2 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -248,6 +248,14 @@ struct PlannerInfo
 	 */
 	struct AppendRelInfo **append_rel_array pg_node_attr(read_write_ignore);
 
+	/*
+	 * top_parent_relid_array is the same length as simple_rel_array and holds
+	 * the top-level parent indexes of the corresponding rels within
+	 * simple_rel_array. The element can be zero if the rel has no parents,
+	 * i.e., is itself in a top-level.
+	 */
+	Index	   *top_parent_relid_array pg_node_attr(read_write_ignore);
+
 	/*
 	 * all_baserels is a Relids set of all base relids (but not joins or
 	 * "other" rels) in the query.  This is computed in deconstruct_jointree.
@@ -957,6 +965,17 @@ typedef struct RelOptInfo
 	/* Bitmask of optional features supported by the table AM */
 	uint32		amflags;
 
+	/*
+	 * information about a join rel
+	 */
+	/* index in root->join_rel_list of this rel */
+	Index		join_rel_list_index;
+
+	/*
+	 * EquivalenceMembers referencing this rel
+	 */
+	List	   *eclass_child_members;
+
 	/*
 	 * Information about foreign tables and foreign joins
 	 */
@@ -1374,6 +1393,12 @@ typedef struct JoinDomain
  * entry: consider SELECT random() AS a, random() AS b ... ORDER BY b,a.
  * So we record the SortGroupRef of the originating sort clause.
  *
+ * EquivalenceClass->ec_members can only have parent members, and child members
+ * are stored in RelOptInfos, from which those child members are translated. To
+ * lookup child EquivalenceMembers, we use EquivalenceChildMemberIterator. See
+ * its comment for usage. The approach to lookup child members quickly is
+ * described as iterate_child_rel_equivalences() comment.
+ *
  * NB: if ec_merged isn't NULL, this class has been merged into another, and
  * should be ignored in favor of using the pointed-to class.
  *
@@ -1446,10 +1471,46 @@ typedef struct EquivalenceMember
 	bool		em_is_child;	/* derived version for a child relation? */
 	Oid			em_datatype;	/* the "nominal type" used by the opfamily */
 	JoinDomain *em_jdomain;		/* join domain containing the source clause */
+	Relids		em_child_relids;	/* all relids of child rels */
+	Bitmapset  *em_child_joinrel_relids;	/* indexes in root->join_rel_list
+											 * of join rel children */
 	/* if em_is_child is true, this links to corresponding EM for top parent */
 	struct EquivalenceMember *em_parent pg_node_attr(read_write_ignore);
 } EquivalenceMember;
 
+/*
+ * EquivalenceChildMemberIterator
+ *
+ * EquivalenceClass has only parent EquivalenceMembers, so we need to translate
+ * child members if necessary. EquivalenceChildMemberIterator provides a way to
+ * iterate over translated child members during the loop in addition to all of
+ * the parent EquivalenceMembers.
+ *
+ * The most common way to use this struct is as follows:
+ * -----
+ * EquivalenceClass				   *ec = given;
+ * Relids							rel = given;
+ * EquivalenceChildMemberIterator	it;
+ * EquivalenceMember			   *em;
+ *
+ * setup_eclass_child_member_iterator(&it, ec);
+ * while ((em = eclass_child_member_iterator_next(&it)) != NULL)
+ * {
+ *     use em ...;
+ *     if (we need to iterate over child EquivalenceMembers)
+ *         iterate_child_rel_equivalences(&it, root, ec, em, rel);
+ *     use em ...;
+ * }
+ * dispose_eclass_child_member_iterator(&it);
+ * -----
+ */
+typedef struct
+{
+	int			index;			/* current index within 'ec_members'. */
+	bool		modified;		/* is 'ec_members' a newly allocated one? */
+	List	   *ec_members;		/* parent and child members */
+} EquivalenceChildMemberIterator;
+
 /*
  * PathKeys
  *
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 1035e6560c1..5e79cf1f63b 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -332,6 +332,24 @@ extern Relids min_join_parameterization(PlannerInfo *root,
 extern RelOptInfo *fetch_upper_rel(PlannerInfo *root, UpperRelationKind kind,
 								   Relids relids);
 extern Relids find_childrel_parents(PlannerInfo *root, RelOptInfo *rel);
+
+/*
+ * find_relids_top_parents
+ *	  Compute the set of top-parent relids of rel.
+ *
+ * Replaces all Relids appearing in the given 'relids' as their top-level
+ * parents. The result will be NULL if and only if all of the given relids are
+ * top-level.
+ *
+ * The motivation for having this feature as a macro rather than a function is
+ * that Relids are top-level in most cases. We can quickly determine when
+ * root->top_parent_relid_array is NULL.
+ */
+#define find_relids_top_parents(root, relids) \
+	(likely((root)->top_parent_relid_array == NULL) \
+	 ? NULL : find_relids_top_parents_slow(root, relids))
+extern Relids find_relids_top_parents_slow(PlannerInfo *root, Relids relids);
+
 extern ParamPathInfo *get_baserel_parampathinfo(PlannerInfo *root,
 												RelOptInfo *baserel,
 												Relids required_outer);
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 54869d44013..50812e3a5d8 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -137,7 +137,8 @@ extern EquivalenceClass *get_eclass_for_sort_expr(PlannerInfo *root,
 												  Index sortref,
 												  Relids rel,
 												  bool create_it);
-extern EquivalenceMember *find_ec_member_matching_expr(EquivalenceClass *ec,
+extern EquivalenceMember *find_ec_member_matching_expr(PlannerInfo *root,
+													   EquivalenceClass *ec,
 													   Expr *expr,
 													   Relids relids);
 extern EquivalenceMember *find_computable_ec_member(PlannerInfo *root,
@@ -179,6 +180,15 @@ extern void add_setop_child_rel_equivalences(PlannerInfo *root,
 											 RelOptInfo *child_rel,
 											 List *child_tlist,
 											 List *setop_pathkeys);
+extern void setup_eclass_child_member_iterator(EquivalenceChildMemberIterator *it,
+											   EquivalenceClass *ec);
+extern EquivalenceMember *eclass_child_member_iterator_next(EquivalenceChildMemberIterator *it);
+extern void dispose_eclass_child_member_iterator(EquivalenceChildMemberIterator *it);
+extern void iterate_child_rel_equivalences(EquivalenceChildMemberIterator *it,
+										   PlannerInfo *root,
+										   EquivalenceClass *ec,
+										   EquivalenceMember *parent_em,
+										   Relids child_relids);
 extern List *generate_implied_equalities_for_column(PlannerInfo *root,
 													RelOptInfo *rel,
 													ec_matches_callback_type callback,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 2d4c870423a..32e4be0f47f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -683,6 +683,7 @@ EphemeralNamedRelation
 EphemeralNamedRelationData
 EphemeralNamedRelationMetadata
 EphemeralNamedRelationMetadataData
+EquivalenceChildMemberIterator
 EquivalenceClass
 EquivalenceMember
 ErrorContextCallback
-- 
2.45.2.windows.1