From e851d7220d45251fb932fe4e1bb874f63705fcf0 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 v35 2/5] 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     |  15 +-
 src/backend/optimizer/path/equivclass.c | 435 +++++++++++++++++++-----
 src/backend/optimizer/path/indxpath.c   |  15 +-
 src/backend/optimizer/path/pathkeys.c   |   9 +-
 src/backend/optimizer/util/relnode.c    |  33 +-
 src/include/nodes/pathnodes.h           |  79 +++++
 src/include/optimizer/paths.h           |   6 +
 src/tools/pgindent/typedefs.list        |   2 +
 8 files changed, 493 insertions(+), 101 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 6beae0fa37f..adcf6417468 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -7828,14 +7828,13 @@ conversion_error_callback(void *arg)
 EquivalenceMember *
 find_em_for_rel(PlannerInfo *root, EquivalenceClass *ec, RelOptInfo *rel)
 {
-	ListCell   *lc;
-
 	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private;
+	EquivalenceMemberIterator it;
+	EquivalenceMember *em;
 
-	foreach(lc, ec->ec_members)
+	setup_eclass_member_iterator_with_children(&it, root, ec, rel->relids);
+	while ((em = eclass_member_iterator_next(&it)) != NULL)
 	{
-		EquivalenceMember *em = (EquivalenceMember *) lfirst(lc);
-
 		/*
 		 * Note we require !bms_is_empty, else we'd accept constant
 		 * expressions which are not suitable for the purpose.
@@ -7846,6 +7845,7 @@ find_em_for_rel(PlannerInfo *root, EquivalenceClass *ec, RelOptInfo *rel)
 			is_foreign_expr(root, rel, em->em_expr))
 			return em;
 	}
+	dispose_eclass_member_iterator(&it);
 
 	return NULL;
 }
@@ -7899,9 +7899,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 7fe7cdff468..8cce165e95d 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,10 @@ 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_eclass_child_members_to_iterator(EquivalenceMemberIterator *it,
+												 PlannerInfo *root,
+												 EquivalenceClass *ec,
+												 RelOptInfo *child_rel);
 static Bitmapset *get_eclass_indexes_for_relids(PlannerInfo *root,
 												Relids relids);
 static Bitmapset *get_common_eclass_indexes(PlannerInfo *root, Relids relids1,
@@ -268,7 +276,8 @@ process_equivalence(PlannerInfo *root,
 		{
 			EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2);
 
-			Assert(!cur_em->em_is_child);	/* no children yet */
+			/* Child members should not exist in ec_members */
+			Assert(!cur_em->em_is_child);
 
 			/*
 			 * Match constants only within the same JoinDomain (see
@@ -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,7 @@ add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids,
 	em->em_datatype = datatype;
 	em->em_jdomain = jdomain;
 	em->em_parent = parent;
+	em->em_ec = ec;
 
 	if (bms_is_empty(relids))
 	{
@@ -545,11 +560,30 @@ 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(),
+ * add_child_join_rel_equivalences() and add_setop_child_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
@@ -616,7 +650,8 @@ get_eclass_for_sort_expr(PlannerInfo *root,
 	foreach(lc1, root->eq_classes)
 	{
 		EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc1);
-		ListCell   *lc2;
+		EquivalenceMemberIterator it;
+		EquivalenceMember *cur_em;
 
 		/*
 		 * Never match to a volatile EC, except when we are looking at another
@@ -631,10 +666,9 @@ get_eclass_for_sort_expr(PlannerInfo *root,
 		if (!equal(opfamilies, cur_ec->ec_opfamilies))
 			continue;
 
-		foreach(lc2, cur_ec->ec_members)
+		setup_eclass_member_iterator_with_children(&it, root, cur_ec, rel);
+		while ((cur_em = eclass_member_iterator_next(&it)) != NULL)
 		{
-			EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2);
-
 			/*
 			 * Ignore child members unless they match the request.
 			 */
@@ -653,6 +687,7 @@ get_eclass_for_sort_expr(PlannerInfo *root,
 				equal(expr, cur_em->em_expr))
 				return cur_ec;	/* Match! */
 		}
+		dispose_eclass_member_iterator(&it);
 	}
 
 	/* No match; does caller want a NULL result? */
@@ -690,7 +725,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
@@ -764,15 +799,16 @@ find_ec_member_matching_expr(PlannerInfo *root, EquivalenceClass *ec,
 							 Expr *expr,
 							 Relids relids)
 {
-	ListCell   *lc;
+	EquivalenceMemberIterator it;
+	EquivalenceMember *em;
 
 	/* We ignore binary-compatible relabeling on both ends */
 	while (expr && IsA(expr, RelabelType))
 		expr = ((RelabelType *) expr)->arg;
 
-	foreach(lc, ec->ec_members)
+	setup_eclass_member_iterator_with_children(&it, root, ec, relids);
+	while ((em = eclass_member_iterator_next(&it)) != NULL)
 	{
-		EquivalenceMember *em = (EquivalenceMember *) lfirst(lc);
 		Expr	   *emexpr;
 
 		/*
@@ -799,6 +835,7 @@ find_ec_member_matching_expr(PlannerInfo *root, EquivalenceClass *ec,
 		if (equal(emexpr, expr))
 			return em;
 	}
+	dispose_eclass_member_iterator(&it);
 
 	return NULL;
 }
@@ -841,7 +878,8 @@ find_computable_ec_member(PlannerInfo *root,
 						  bool require_parallel_safe)
 {
 	List	   *exprvars;
-	ListCell   *lc;
+	EquivalenceMemberIterator it;
+	EquivalenceMember *em;
 
 	/*
 	 * Pull out the Vars and quasi-Vars present in "exprs".  In the typical
@@ -855,9 +893,9 @@ find_computable_ec_member(PlannerInfo *root,
 							   PVC_INCLUDE_PLACEHOLDERS |
 							   PVC_INCLUDE_CONVERTROWTYPES);
 
-	foreach(lc, ec->ec_members)
+	setup_eclass_member_iterator_with_children(&it, root, ec, relids);
+	while ((em = eclass_member_iterator_next(&it)) != NULL)
 	{
-		EquivalenceMember *em = (EquivalenceMember *) lfirst(lc);
 		List	   *emvars;
 		ListCell   *lc2;
 
@@ -901,6 +939,7 @@ find_computable_ec_member(PlannerInfo *root,
 
 		return em;				/* found usable expression */
 	}
+	dispose_eclass_member_iterator(&it);
 
 	return NULL;
 }
@@ -1162,7 +1201,8 @@ generate_base_implied_equalities_const(PlannerInfo *root,
 		Oid			eq_op;
 		RestrictInfo *rinfo;
 
-		Assert(!cur_em->em_is_child);	/* no children yet */
+		Assert(!cur_em->em_is_child);	/* Child members should not exist in
+										 * ec_members */
 		if (cur_em == const_em)
 			continue;
 		eq_op = select_equality_operator(ec,
@@ -1231,7 +1271,8 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 		EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc);
 		int			relid;
 
-		Assert(!cur_em->em_is_child);	/* no children yet */
+		Assert(!cur_em->em_is_child);	/* Child members should not exist in
+										 * ec_members */
 		if (!bms_get_singleton_member(cur_em->em_relids, &relid))
 			continue;
 		Assert(relid < root->simple_rel_array_size);
@@ -1564,7 +1605,8 @@ generate_join_implied_equalities_normal(PlannerInfo *root,
 	List	   *new_members = NIL;
 	List	   *outer_members = NIL;
 	List	   *inner_members = NIL;
-	ListCell   *lc1;
+	EquivalenceMemberIterator it;
+	EquivalenceMember *cur_em;
 
 	/*
 	 * First, scan the EC to identify member values that are computable at the
@@ -1575,10 +1617,9 @@ generate_join_implied_equalities_normal(PlannerInfo *root,
 	 * 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.
 	 */
-	foreach(lc1, ec->ec_members)
+	setup_eclass_member_iterator_with_children(&it, root, ec, join_relids);
+	while ((cur_em = eclass_member_iterator_next(&it)) != NULL)
 	{
-		EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc1);
-
 		/*
 		 * We don't need to check explicitly for child EC members.  This test
 		 * against join_relids will cause them to be ignored except when
@@ -1594,6 +1635,7 @@ generate_join_implied_equalities_normal(PlannerInfo *root,
 		else
 			new_members = lappend(new_members, cur_em);
 	}
+	dispose_eclass_member_iterator(&it);
 
 	/*
 	 * First, select the joinclause if needed.  We can equate any one outer
@@ -1611,6 +1653,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)
 		{
@@ -1685,6 +1728,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)
@@ -1692,7 +1736,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)
 			{
@@ -2181,7 +2225,8 @@ reconsider_outer_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo,
 		{
 			EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2);
 
-			Assert(!cur_em->em_is_child);	/* no children yet */
+			Assert(!cur_em->em_is_child);	/* Child members should not exist
+											 * in ec_members */
 			if (equal(outervar, cur_em->em_expr))
 			{
 				match = true;
@@ -2308,7 +2353,8 @@ reconsider_full_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo)
 		foreach(lc2, cur_ec->ec_members)
 		{
 			coal_em = (EquivalenceMember *) lfirst(lc2);
-			Assert(!coal_em->em_is_child);	/* no children yet */
+			Assert(!coal_em->em_is_child);	/* Child members should not exist
+											 * in ec_members */
 			if (IsA(coal_em->em_expr, CoalesceExpr))
 			{
 				CoalesceExpr *cexpr = (CoalesceExpr *) coal_em->em_expr;
@@ -2526,8 +2572,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 */
+			Assert(!em->em_is_child);	/* Child members should not exist in
+										 * ec_members */
 			if (equal(item1, em->em_expr))
 				item1member = true;
 			else if (equal(item2, em->em_expr))
@@ -2598,8 +2644,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;
@@ -2696,6 +2742,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
@@ -2708,7 +2755,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
@@ -2721,29 +2767,15 @@ 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 */
 
-			/*
-			 * We consider only original EC members here, not
-			 * already-transformed child members.  Otherwise, if some original
-			 * member expression references more than one appendrel, we'd get
-			 * an O(N^2) explosion of useless derived expressions for
-			 * 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
@@ -2759,6 +2791,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)
 				{
@@ -2788,9 +2821,11 @@ 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);
 
 				/* Record this EC index for the child rel */
 				child_rel->eclass_indexes = bms_add_member(child_rel->eclass_indexes, i);
@@ -2840,7 +2875,7 @@ 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;
+		ListCell   *lc;
 
 		/*
 		 * If this EC contains a volatile expression, then generating child
@@ -2853,25 +2888,15 @@ 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 */
 
-			/*
-			 * 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,
@@ -2886,6 +2911,8 @@ add_child_join_rel_equivalences(PlannerInfo *root,
 				/* Yes, generate transformed child version */
 				Expr	   *child_expr;
 				Relids		new_relids;
+				EquivalenceMember *child_em;
+				int			j;
 
 				if (parent_joinrel->reloptkind == RELOPT_JOINREL)
 				{
@@ -2916,9 +2943,38 @@ 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);
+
+				/*
+				 * Update the corresponding inverted indexes.
+				 */
+				j = -1;
+				while ((j = bms_next_member(child_joinrel->relids, j)) >= 0)
+				{
+					EquivalenceClassIndexes *indexes =
+						&root->eclass_indexes_array[j];
+
+					/*
+					 * We do not need to update the inverted index of the top
+					 * parent relations. This is because EquivalenceMembers
+					 * that have only such parent relations as em_relids are
+					 * already present in the ec_members, and so cannot be
+					 * candidates for additional iteration by
+					 * EquivalenceMemberIterator. Since the iterator needs
+					 * EquivalenceMembers whose em_relids has child relations,
+					 * skipping the update of this inverted index allows for
+					 * faster iteration.
+					 */
+					if (root->append_rel_array[j] == NULL)
+						continue;
+					indexes->joinrel_indexes =
+						bms_add_member(indexes->joinrel_indexes,
+									   child_joinrel->join_rel_list_index);
+				}
 			}
 		}
 	}
@@ -2971,7 +3027,7 @@ add_setop_child_rel_equivalences(PlannerInfo *root, RelOptInfo *child_rel,
 					  tle->expr,
 					  child_rel->relids,
 					  parent_em->em_jdomain,
-					  parent_em,
+					  /* parent_em, */ /* Will be fixed in the next commit*/
 					  exprType((Node *) tle->expr));
 
 		lc2 = lnext(setop_pathkeys, lc2);
@@ -2987,6 +3043,219 @@ add_setop_child_rel_equivalences(PlannerInfo *root, RelOptInfo *child_rel,
 											  list_length(root->eq_classes) - 1);
 }
 
+/*
+ * setup_eclass_member_iterator_with_children
+ *	  Setup an EquivalenceMemberIterator 'it' to iterate over all parent
+ *	  EquivalenceMembers and child members associated with the given 'ec' that
+ *	  are relevant to the specified 'relids'.
+ *
+ * This iterator returns:
+ *	- All parent members stored directly in ec->ec_members.
+ *	- The child members whose em_relids is a subset of the given 'relids'.
+ *
+ * Note:
+ *	- The iterator may return false positives, i.e., child members whose
+ *	  em_relids is not a subset. So the caller must check that they satisfy
+ *	  the desired condition.
+ *	- Once used, the caller should dispose of the iterator by calling
+ *	  dispose_eclass_member_iterator().
+ *
+ * Parameters:
+ *	root - The PlannerInfo context.
+ *	ec - The EquivalenceClass from which to iterate members.
+ *	relids - The Relids used to filter for relevant child members.
+ */
+void
+setup_eclass_member_iterator_with_children(EquivalenceMemberIterator *it,
+										   PlannerInfo *root,
+										   EquivalenceClass *ec,
+										   Relids relids)
+{
+	Bitmapset  *matching_indexes;
+	int			i;
+
+	/*
+	 * Initialize the iterator.
+	 */
+	it->index = -1;
+	it->list_is_copy = false;
+	it->ec_members = ec->ec_members;
+
+	/*
+	 * If there are no child relations, there is nothing to do. This
+	 * effectively avoids regression for non-partitioned cases.
+	 */
+	if (root->append_rel_array == NULL)
+		return;
+
+	/*
+	 * 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(),
+	 * add_child_join_rel_equivalences(), and
+	 * add_setop_child_rel_equivalences(). To retrieve child
+	 * EquivalenceMembers of some parent, we need to know which RelOptInfos
+	 * have such child members. We can know this information using indexes
+	 * like EquivalenceClassIndexes->joinrel_indexes.
+	 *
+	 * We use an inverted index mechanism to quickly iterate over the members
+	 * whose em_relids is a subset of the given 'relids'. The inverted indexes
+	 * store RelOptInfo indices that have EquivalenceMembers mentioning them.
+	 * Taking the union of these indexes allows to find which RelOptInfos have
+	 * the EquivalenceMember we are looking for. With this method, the
+	 * em_relids of the newly iterated ones overlap the given 'relids', but
+	 * may not be subsets, so the caller must check that they satisfy the
+	 * desired condition.
+	 *
+	 * The above comments are about joinrels, and for simple rels, this
+	 * mechanism is simpler. It is sufficient to simply add the child
+	 * EquivalenceMembers of RelOptInfo to the iterator.
+	 *
+	 * We need to perform these steps for each of the two types of relations.
+	 */
+
+	/*
+	 * Iterate over the given relids, adding child members for simple rels and
+	 * taking union indexes for join rels.
+	 */
+	i = -1;
+	matching_indexes = NULL;
+	while ((i = bms_next_member(relids, i)) >= 0)
+	{
+		RelOptInfo *child_rel;
+		EquivalenceClassIndexes *indexes;
+
+		/*
+		 * If this relation is a parent, we don't have to do anything.
+		 */
+		if (root->append_rel_array[i] == NULL)
+			continue;
+
+		/*
+		 * Add child members that mention this relation to the iterator.
+		 */
+		child_rel = root->simple_rel_array[i];
+		if (child_rel != NULL)
+			add_eclass_child_members_to_iterator(it, root, ec, child_rel);
+
+		/*
+		 * Union indexes for join rels.
+		 */
+		indexes = &root->eclass_indexes_array[i];
+		matching_indexes =
+			bms_add_members(matching_indexes, indexes->joinrel_indexes);
+	}
+
+	/*
+	 * For join rels, add child members using 'matching_indexes'.
+	 */
+	i = -1;
+	while ((i = bms_next_member(matching_indexes, i)) >= 0)
+	{
+		RelOptInfo *child_joinrel =
+			list_nth_node(RelOptInfo, root->join_rel_list, i);
+
+		Assert(child_joinrel != NULL);
+
+		/*
+		 * If this joinrel's Relids is not a subset of the given one, then the
+		 * child EquivalenceMembers it holds should never be a subset either.
+		 */
+		if (bms_is_subset(child_joinrel->relids, relids))
+			add_eclass_child_members_to_iterator(it, root, ec, child_joinrel);
+#ifdef USE_ASSERT_CHECKING
+		else
+		{
+			/*
+			 * Verify that the above comment is correct.
+			 *
+			 * NOTE: We may remove this assertion after the beta process.
+			 */
+
+			ListCell   *lc;
+
+			foreach(lc, child_joinrel->eclass_child_members)
+			{
+				EquivalenceMember *child_em = lfirst_node(EquivalenceMember, lc);
+
+				if (child_em->em_ec != ec)
+					continue;
+				Assert(!bms_is_subset(child_em->em_relids, relids));
+			}
+		}
+#endif
+	}
+	bms_free(matching_indexes);
+}
+
+/*
+ * add_eclass_child_members_to_iterator
+ *	  Add a child EquivalenceMember referencing the given child_rel to
+ *	  the iterator from RelOptInfo.
+ *
+ * This function is expected to be called only from
+ * setup_eclass_member_iterator_with_children().
+ */
+static void
+add_eclass_child_members_to_iterator(EquivalenceMemberIterator *it,
+									 PlannerInfo *root,
+									 EquivalenceClass *ec,
+									 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_ec != ec)
+			continue;
+
+		/*
+		 * If this is the first time the iterator's list has been modified, we
+		 * need to make a copy of it.
+		 */
+		if (!it->list_is_copy)
+		{
+			it->ec_members = list_copy(it->ec_members);
+			it->list_is_copy = true;
+		}
+
+		/* Add this child EquivalenceMember to the list */
+		it->ec_members = lappend(it->ec_members, child_em);
+	}
+}
+
+/*
+ * eclass_member_iterator_next
+ *	  Get a next EquivalenceMember from an EquivalenceMemberIterator 'it'
+ *	  that was setup by setup_eclass_member_iterator_with_children(). NULL is
+ *	  returned if there are no members left.
+ */
+EquivalenceMember *
+eclass_member_iterator_next(EquivalenceMemberIterator *it)
+{
+	if (++it->index < list_length(it->ec_members))
+		return list_nth_node(EquivalenceMember, it->ec_members, it->index);
+	return NULL;
+}
+
+/*
+ * dispose_eclass_member_iterator
+ *	  Free any memory allocated by the iterator.
+ */
+void
+dispose_eclass_member_iterator(EquivalenceMemberIterator *it)
+{
+	/*
+	 * XXX Should we use list_free()? I decided to use this style to take
+	 * advantage of speculative execution.
+	 */
+	if (unlikely(it->list_is_copy))
+		pfree(it->ec_members);
+}
+
 
 /*
  * generate_implied_equalities_for_column
@@ -3041,6 +3310,7 @@ generate_implied_equalities_for_column(PlannerInfo *root,
 		EquivalenceClass *cur_ec = (EquivalenceClass *) list_nth(root->eq_classes, i);
 		EquivalenceMember *cur_em;
 		ListCell   *lc2;
+		EquivalenceMemberIterator it;
 
 		/* Sanity check eclass_indexes only contain ECs for rel */
 		Assert(is_child_rel || bms_is_subset(rel->relids, cur_ec->ec_relids));
@@ -3062,15 +3332,14 @@ generate_implied_equalities_for_column(PlannerInfo *root,
 		 * corner cases, so for now we live with just reporting the first
 		 * match.  See also get_eclass_for_sort_expr.)
 		 */
-		cur_em = NULL;
-		foreach(lc2, cur_ec->ec_members)
+		setup_eclass_member_iterator_with_children(&it, root, cur_ec, rel->relids);
+		while ((cur_em = eclass_member_iterator_next(&it)) != NULL)
 		{
-			cur_em = (EquivalenceMember *) lfirst(lc2);
 			if (bms_equal(cur_em->em_relids, rel->relids) &&
 				callback(root, rel, cur_ec, cur_em, callback_arg))
 				break;
-			cur_em = NULL;
 		}
+		dispose_eclass_member_iterator(&it);
 
 		if (!cur_em)
 			continue;
@@ -3085,8 +3354,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 ||
@@ -3304,8 +3573,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 a43ca16d683..c206f260633 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))
@@ -3739,7 +3739,7 @@ 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)
 {
@@ -3756,7 +3756,8 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 	{
 		PathKey    *pathkey = (PathKey *) lfirst(lc1);
 		bool		found = false;
-		ListCell   *lc2;
+		EquivalenceMemberIterator it;
+		EquivalenceMember *member;
 
 
 		/* Pathkey must request default sort order for the target opfamily */
@@ -3776,9 +3777,10 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 		 * be considered to match more than one pathkey list, which is OK
 		 * here.  See also get_eclass_for_sort_expr.)
 		 */
-		foreach(lc2, pathkey->pk_eclass->ec_members)
+		setup_eclass_member_iterator_with_children(&it, root, pathkey->pk_eclass,
+												   index->rel->relids);
+		while ((member = eclass_member_iterator_next(&it)) != NULL)
 		{
-			EquivalenceMember *member = (EquivalenceMember *) lfirst(lc2);
 			int			indexcol;
 
 			/* No possibility of match if it references other relations */
@@ -3813,6 +3815,7 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 			if (found)			/* don't want to look at remaining members */
 				break;
 		}
+		dispose_eclass_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 154eb505d75..a9419d37e2f 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/util/relnode.c b/src/backend/optimizer/util/relnode.c
index ff507331a06..7791e282e9e 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -119,16 +119,23 @@ setup_simple_rel_arrays(PlannerInfo *root)
 		root->simple_rte_array[rti++] = rte;
 	}
 
-	/* append_rel_array is not needed if there are no AppendRelInfos */
+	/*
+	 * append_rel_array and eclass_indexes_array are not needed if there are
+	 * no AppendRelInfos.
+	 */
 	if (root->append_rel_list == NIL)
 	{
 		root->append_rel_array = NULL;
+		root->eclass_indexes_array = NULL;
 		return;
 	}
 
 	root->append_rel_array = (AppendRelInfo **)
 		palloc0(size * sizeof(AppendRelInfo *));
 
+	root->eclass_indexes_array = (EquivalenceClassIndexes *)
+		palloc0(size * sizeof(EquivalenceClassIndexes));
+
 	/*
 	 * append_rel_array is filled with any already-existing AppendRelInfos,
 	 * which currently could only come from UNION ALL flattening.  We might
@@ -175,11 +182,21 @@ 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)
+	{
+		Assert(root->eclass_indexes_array);
 		root->append_rel_array =
 			repalloc0_array(root->append_rel_array, AppendRelInfo *, root->simple_rel_array_size, new_size);
+		root->eclass_indexes_array =
+			repalloc0_array(root->eclass_indexes_array, EquivalenceClassIndexes, root->simple_rel_array_size, new_size);
+	}
 	else
+	{
+		Assert(!root->eclass_indexes_array);
 		root->append_rel_array =
 			palloc0_array(AppendRelInfo *, new_size);
+		root->eclass_indexes_array =
+			palloc0_array(EquivalenceClassIndexes, new_size);
+	}
 
 	root->simple_rel_array_size = new_size;
 }
@@ -234,6 +251,8 @@ 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->join_rel_list_index = -1;
+	rel->eclass_child_members = NIL;
 	rel->serverid = InvalidOid;
 	if (rte->rtekind == RTE_RELATION)
 	{
@@ -626,6 +645,12 @@ set_foreign_rel_properties(RelOptInfo *joinrel, RelOptInfo *outer_rel,
 static void
 add_join_rel(PlannerInfo *root, RelOptInfo *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);
+
 	/* GEQO requires us to append the new joinrel to the end of the list! */
 	root->join_rel_list = lappend(root->join_rel_list, joinrel);
 
@@ -741,6 +766,8 @@ build_join_rel(PlannerInfo *root,
 	joinrel->subplan_params = NIL;
 	joinrel->rel_parallel_workers = -1;
 	joinrel->amflags = 0;
+	joinrel->join_rel_list_index = -1;
+	joinrel->eclass_child_members = NIL;
 	joinrel->serverid = InvalidOid;
 	joinrel->userid = InvalidOid;
 	joinrel->useridiscurrent = false;
@@ -928,6 +955,8 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
 	joinrel->subroot = NULL;
 	joinrel->subplan_params = NIL;
 	joinrel->amflags = 0;
+	joinrel->join_rel_list_index = -1;
+	joinrel->eclass_child_members = NIL;
 	joinrel->serverid = InvalidOid;
 	joinrel->userid = InvalidOid;
 	joinrel->useridiscurrent = false;
@@ -1490,6 +1519,8 @@ 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->join_rel_list_index = -1;
+	upperrel->eclass_child_members = NIL;
 
 	root->upper_rels[kind] = lappend(root->upper_rels[kind], upperrel);
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index c24a1fc8514..6b3a028cc58 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -216,6 +216,8 @@ typedef struct PlannerInfo PlannerInfo;
 #define HAVE_PLANNERINFO_TYPEDEF 1
 #endif
 
+struct EquivalenceClassIndexes;
+
 struct PlannerInfo
 {
 	pg_node_attr(no_copy_equal, no_read, no_query_jumble)
@@ -272,6 +274,13 @@ struct PlannerInfo
 	 */
 	struct AppendRelInfo **append_rel_array pg_node_attr(read_write_ignore);
 
+	/*
+	 * eclass_indexes_array is the same length as simple_rel_array and holds
+	 * the indexes of the corresponding rels for faster EquivalenceMember
+	 * lookups. See the EquivalenceClass comment for more details.
+	 */
+	struct EquivalenceClassIndexes *eclass_indexes_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.
@@ -984,6 +993,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 */
+	int			join_rel_list_index;
+
+	/*
+	 * EquivalenceMembers referencing this rel
+	 */
+	List	   *eclass_child_members;
+
 	/*
 	 * Information about foreign tables and foreign joins
 	 */
@@ -1403,6 +1423,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 EquivalenceMemberIterator. See
+ * its comment for usage. The approach to lookup child members quickly is
+ * described as setup_eclass_member_iterator_with_children() 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.
  *
@@ -1477,8 +1503,61 @@ typedef struct EquivalenceMember
 	JoinDomain *em_jdomain;		/* join domain containing the source clause */
 	/* if em_is_child is true, this links to corresponding EM for top parent */
 	struct EquivalenceMember *em_parent pg_node_attr(read_write_ignore);
+	EquivalenceClass *em_ec;	/* EquivalenceClass which has this member */
 } EquivalenceMember;
 
+/*
+ * EquivalenceMemberIterator
+ *
+ * EquivalenceMemberIterator is designed to iterate over all parent
+ * EquivalenceMembers and child members associated with the given 'ec' that
+ * are relevant to the specified 'relids'. In particular, it iterates over:
+ *	- All parent members stored directly in ec->ec_members.
+ *	- The child members whose em_relids is a subset of the given 'relids'.
+ *
+ * Note:
+ *	- The iterator may return false positives, i.e., child members whose
+ *	  em_relids is not a subset. So the caller must check that they satisfy
+ *	  the desired condition.
+ *
+ * The most common way to use this iterator is as follows:
+ * -----
+ * PlannerInfo					   *root = given;
+ * EquivalenceClass				   *ec = given;
+ * Relids							rel = given;
+ * EquivalenceMemberIterator		it;
+ * EquivalenceMember			   *em;
+ *
+ * setup_eclass_member_iterator_with_children(&it, root, ec, rel);
+ * while ((em = eclass_member_iterator_next(&it)) != NULL)
+ * {
+ *     use em ...;
+ * }
+ * dispose_eclass_member_iterator(&it);
+ * -----
+ */
+typedef struct
+{
+	int			index;			/* current index within 'ec_members'. */
+	bool		list_is_copy;	/* is 'ec_members' a newly allocated one? */
+	List	   *ec_members;		/* parent and child members */
+} EquivalenceMemberIterator;
+
+/*
+ * EquivalenceClassIndexes
+ *
+ * As mentioned in the EquivalenceClass comment, we introduce a
+ * bitmapset-based indexing mechanism for faster lookups of child
+ * EquivalenceMembers. This struct exists for each relation and holds the
+ * corresponding indexes.
+ */
+typedef struct EquivalenceClassIndexes
+{
+	Bitmapset  *joinrel_indexes;	/* Indexes in PlannerInfo's join_rel_list
+									 * list for RelOptInfos that mention this
+									 * relation */
+} EquivalenceClassIndexes;
+
 /*
  * PathKeys
  *
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 84a000a3ef1..6b80c8665b9 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -183,6 +183,12 @@ extern void add_setop_child_rel_equivalences(PlannerInfo *root,
 											 RelOptInfo *child_rel,
 											 List *child_tlist,
 											 List *setop_pathkeys);
+extern void setup_eclass_member_iterator_with_children(EquivalenceMemberIterator *it,
+													   PlannerInfo *root,
+													   EquivalenceClass *ec,
+													   Relids relids);
+extern EquivalenceMember *eclass_member_iterator_next(EquivalenceMemberIterator *it);
+extern void dispose_eclass_member_iterator(EquivalenceMemberIterator *it);
 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 bfa276d2d35..c9688aa6b67 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -700,7 +700,9 @@ EphemeralNamedRelationData
 EphemeralNamedRelationMetadata
 EphemeralNamedRelationMetadataData
 EquivalenceClass
+EquivalenceClassIndexes
 EquivalenceMember
+EquivalenceMemberIterator
 ErrorContextCallback
 ErrorData
 ErrorSaveContext
-- 
2.45.2.windows.1

