From 292be23bdb681548298038ee7ac2c7a86180f282 Mon Sep 17 00:00:00 2001
From: "dgrowley@gmail.com" <dgrowley@gmail.com>
Date: Thu, 18 Jul 2019 17:24:19 +1200
Subject: [PATCH 1/2] Speed up finding EquivalenceClasses for a given set of
 rels

Previously in order to determine which EquivalenceClasses a relation had
members in, we had to loop over all EquivalenceClasses stored in
PlannerInfo's eq_classes and check if ec_relids mentioned the relation.
For the most part, this was fine, as generally unless queries were fairly
complex, the overhead of performing the lookup would have not been that
significant.  However, when queries contained large numbers of joins and
many EquivalenceClasses existed, the overhead to find the set of classes
matching a given set of relations could become a significant portion of
the overall planning effort.

Here we allow a much more efficient method to access the
EquivalenceClasses which match a given relation, or set of relations.  A
new Bitmapset field in RelOptInfo now exists to store the indexes into
PlannerInfo's eq_classes list which each relation is mentioned in.  This
allows very fast lookups to find all classes belonging to a single
relation.  When we need to lookup classes belonging to a given pair of
relations, we can simply bitwise-AND the Bitmapsets from each relation and
use the result to directly look up the common EquivalenceClasses.

This was originally intended to fix the performance penalty of looking up
foreign keys matching a join condition which was introduced by 100340e2d.
However, we're speeding up much more than just that here.

Author: David Rowley and Tom Lane
Discussion: https://postgr.es/m/6970.1545327857@sss.pgh.pa.us
---
 src/backend/nodes/outfuncs.c              |   2 +
 src/backend/optimizer/path/equivclass.c   | 252 +++++++++++++++++-----
 src/backend/optimizer/path/pathkeys.c     |   8 +-
 src/backend/optimizer/plan/planmain.c     |   6 +
 src/backend/optimizer/plan/planner.c      |   1 +
 src/backend/optimizer/prep/prepjointree.c |   1 +
 src/backend/optimizer/prep/prepunion.c    |   9 +
 src/backend/optimizer/util/relnode.c      |   3 +
 src/include/nodes/pathnodes.h             |   6 +
 9 files changed, 228 insertions(+), 60 deletions(-)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8e31fae47f..86c31a48c9 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2195,6 +2195,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
 	WRITE_NODE_FIELD(cte_plan_ids);
 	WRITE_NODE_FIELD(multiexpr_params);
 	WRITE_NODE_FIELD(eq_classes);
+	WRITE_BOOL_FIELD(ec_merging_done);
 	WRITE_NODE_FIELD(canon_pathkeys);
 	WRITE_NODE_FIELD(left_join_clauses);
 	WRITE_NODE_FIELD(right_join_clauses);
@@ -2261,6 +2262,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node)
 	WRITE_UINT_FIELD(pages);
 	WRITE_FLOAT_FIELD(tuples, "%.0f");
 	WRITE_FLOAT_FIELD(allvisfrac, "%.6f");
+	WRITE_BITMAPSET_FIELD(eclass_indexes);
 	WRITE_NODE_FIELD(subroot);
 	WRITE_NODE_FIELD(subplan_params);
 	WRITE_INT_FIELD(rel_parallel_workers);
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 78d076b13c..6e7db4b969 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -64,6 +64,10 @@ static bool reconsider_outer_join_clause(PlannerInfo *root,
 										 bool outer_on_left);
 static bool reconsider_full_join_clause(PlannerInfo *root,
 										RestrictInfo *rinfo);
+static Bitmapset *get_eclass_indexes_for_relids(PlannerInfo *root,
+												Relids relids);
+static Bitmapset *get_common_eclass_indexes(PlannerInfo *root, Relids relids1,
+											Relids relids2);
 
 
 /*
@@ -341,10 +345,11 @@ process_equivalence(PlannerInfo *root,
 
 		/*
 		 * Case 2: need to merge ec1 and ec2.  This should never happen after
-		 * we've built any canonical pathkeys; if it did, those pathkeys might
-		 * be rendered non-canonical by the merge.
+		 * the ECs have reached canonical state; otherwise, pathkeys could be
+		 * rendered non-canonical by the merge, and relation eclass indexes
+		 * would get broken by removal of an eq_classes list entry.
 		 */
-		if (root->canon_pathkeys != NIL)
+		if (root->ec_merging_done)
 			elog(ERROR, "too late to merge equivalence classes");
 
 		/*
@@ -743,6 +748,26 @@ get_eclass_for_sort_expr(PlannerInfo *root,
 
 	root->eq_classes = lappend(root->eq_classes, newec);
 
+	/*
+	 * If EC merging is already complete, we have to mop up by adding the new
+	 * EC to the eclass_indexes of the relation(s) mentioned in it.
+	 */
+	if (root->ec_merging_done)
+	{
+		int			ec_index = list_length(root->eq_classes) - 1;
+		int			i = -1;
+
+		while ((i = bms_next_member(newec->ec_relids, i)) > 0)
+		{
+			RelOptInfo *rel = root->simple_rel_array[i];
+
+			Assert(rel->reloptkind == RELOPT_BASEREL);
+
+			rel->eclass_indexes = bms_add_member(rel->eclass_indexes,
+												 ec_index);
+		}
+	}
+
 	MemoryContextSwitchTo(oldcontext);
 
 	return newec;
@@ -800,42 +825,71 @@ get_eclass_for_sort_expr(PlannerInfo *root,
 void
 generate_base_implied_equalities(PlannerInfo *root)
 {
+	int			ec_index;
 	ListCell   *lc;
-	Index		rti;
 
+	/*
+	 * At this point, we're done absorbing knowledge of equivalences in the
+	 * query, so no further EC merging should happen, and ECs remaining in the
+	 * eq_classes list can be considered canonical.  (But note that it's still
+	 * possible for new single-member ECs to be added through
+	 * get_eclass_for_sort_expr().)
+	 */
+	root->ec_merging_done = true;
+
+	ec_index = 0;
 	foreach(lc, root->eq_classes)
 	{
 		EquivalenceClass *ec = (EquivalenceClass *) lfirst(lc);
+		bool		can_generate_joinclause = false;
+		int			i;
 
 		Assert(ec->ec_merged == NULL);	/* else shouldn't be in list */
 		Assert(!ec->ec_broken); /* not yet anyway... */
 
-		/* Single-member ECs won't generate any deductions */
-		if (list_length(ec->ec_members) <= 1)
-			continue;
+		/*
+		 * Generate implied equalities that are restriction clauses.
+		 * Single-member ECs won't generate any deductions, either here or at
+		 * the join level.
+		 */
+		if (list_length(ec->ec_members) > 1)
+		{
+			if (ec->ec_has_const)
+				generate_base_implied_equalities_const(root, ec);
+			else
+				generate_base_implied_equalities_no_const(root, ec);
+
+			/* Recover if we failed to generate required derived clauses */
+			if (ec->ec_broken)
+				generate_base_implied_equalities_broken(root, ec);
+
+			/* Detect whether this EC might generate join clauses */
+			can_generate_joinclause =
+				(bms_membership(ec->ec_relids) == BMS_MULTIPLE);
+		}
 
-		if (ec->ec_has_const)
-			generate_base_implied_equalities_const(root, ec);
-		else
-			generate_base_implied_equalities_no_const(root, ec);
+		/*
+		 * Mark the base rels cited in each eclass (which should all exist by
+		 * now) with the eq_classes indexes of all eclasses mentioning them.
+		 * This will let us avoid searching in subsequent lookups.  While
+		 * we're at it, we can mark base rels that have pending eclass joins;
+		 * this is a cheap version of has_relevant_eclass_joinclause().
+		 */
+		i = -1;
+		while ((i = bms_next_member(ec->ec_relids, i)) > 0)
+		{
+			RelOptInfo *rel = root->simple_rel_array[i];
 
-		/* Recover if we failed to generate required derived clauses */
-		if (ec->ec_broken)
-			generate_base_implied_equalities_broken(root, ec);
-	}
+			Assert(rel->reloptkind == RELOPT_BASEREL);
 
-	/*
-	 * This is also a handy place to mark base rels (which should all exist by
-	 * now) with flags showing whether they have pending eclass joins.
-	 */
-	for (rti = 1; rti < root->simple_rel_array_size; rti++)
-	{
-		RelOptInfo *brel = root->simple_rel_array[rti];
+			rel->eclass_indexes = bms_add_member(rel->eclass_indexes,
+												 ec_index);
 
-		if (brel == NULL)
-			continue;
+			if (can_generate_joinclause)
+				rel->has_eclass_joins = true;
+		}
 
-		brel->has_eclass_joins = has_relevant_eclass_joinclause(root, brel);
+		ec_index++;
 	}
 }
 
@@ -2022,12 +2076,24 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root,
 	Index		var2varno = fkinfo->ref_relid;
 	AttrNumber	var2attno = fkinfo->confkey[colno];
 	Oid			eqop = fkinfo->conpfeqop[colno];
+	RelOptInfo *rel1 = root->simple_rel_array[var1varno];
+	RelOptInfo *rel2 = root->simple_rel_array[var2varno];
 	List	   *opfamilies = NIL;	/* compute only if needed */
-	ListCell   *lc1;
-
-	foreach(lc1, root->eq_classes)
+	Bitmapset  *matching_ecs;
+	int			i;
+
+	/* Consider only eclasses mentioning both relations */
+	Assert(root->ec_merging_done);
+	Assert(IS_SIMPLE_REL(rel1));
+	Assert(IS_SIMPLE_REL(rel2));
+	matching_ecs = bms_intersect(rel1->eclass_indexes,
+								 rel2->eclass_indexes);
+
+	i = -1;
+	while ((i = bms_next_member(matching_ecs, i)) >= 0)
 	{
-		EquivalenceClass *ec = (EquivalenceClass *) lfirst(lc1);
+		EquivalenceClass *ec = (EquivalenceClass *) list_nth(root->eq_classes,
+															 i);
 		bool		item1member = false;
 		bool		item2member = false;
 		ListCell   *lc2;
@@ -2037,14 +2103,6 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root,
 			continue;
 		/* Note: it seems okay to match to "broken" eclasses here */
 
-		/*
-		 * If eclass visibly doesn't have members for both rels, there's no
-		 * need to grovel through the members.
-		 */
-		if (!bms_is_member(var1varno, ec->ec_relids) ||
-			!bms_is_member(var2varno, ec->ec_relids))
-			continue;
-
 		foreach(lc2, ec->ec_members)
 		{
 			EquivalenceMember *em = (EquivalenceMember *) lfirst(lc2);
@@ -2104,11 +2162,19 @@ add_child_rel_equivalences(PlannerInfo *root,
 						   RelOptInfo *parent_rel,
 						   RelOptInfo *child_rel)
 {
-	ListCell   *lc1;
+	int			i;
 
-	foreach(lc1, root->eq_classes)
+	/*
+	 * EC merging should be complete already, so we can use the parent rel's
+	 * eclass_indexes to avoid searching all of root->eq_classes.
+	 */
+	Assert(root->ec_merging_done);
+	Assert(IS_SIMPLE_REL(parent_rel));
+
+	i = -1;
+	while ((i = bms_next_member(parent_rel->eclass_indexes, i)) >= 0)
 	{
-		EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc1);
+		EquivalenceClass *cur_ec = (EquivalenceClass *) list_nth(root->eq_classes, i);
 		int			num_members;
 
 		/*
@@ -2205,6 +2271,14 @@ add_child_rel_equivalences(PlannerInfo *root,
 			}
 		}
 	}
+
+	/*
+	 * The child is now mentioned in all the same eclasses as its parent ---
+	 * except for corner cases such as a volatile EC.  But it's okay if
+	 * eclass_indexes lists too many rels, so just make a copy of the parent's
+	 * index set.
+	 */
+	child_rel->eclass_indexes = bms_copy(parent_rel->eclass_indexes);
 }
 
 
@@ -2241,7 +2315,10 @@ generate_implied_equalities_for_column(PlannerInfo *root,
 	List	   *result = NIL;
 	bool		is_child_rel = (rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
 	Relids		parent_relids;
-	ListCell   *lc1;
+	int			i;
+
+	/* Should be OK to rely on eclass_indexes */
+	Assert(root->ec_merging_done);
 
 	/* Indexes are available only on base or "other" member relations. */
 	Assert(IS_SIMPLE_REL(rel));
@@ -2252,9 +2329,10 @@ generate_implied_equalities_for_column(PlannerInfo *root,
 	else
 		parent_relids = NULL;	/* not used, but keep compiler quiet */
 
-	foreach(lc1, root->eq_classes)
+	i = -1;
+	while ((i = bms_next_member(rel->eclass_indexes, i)) >= 0)
 	{
-		EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc1);
+		EquivalenceClass *cur_ec = (EquivalenceClass *) list_nth(root->eq_classes, i);
 		EquivalenceMember *cur_em;
 		ListCell   *lc2;
 
@@ -2265,14 +2343,6 @@ generate_implied_equalities_for_column(PlannerInfo *root,
 		if (cur_ec->ec_has_const || list_length(cur_ec->ec_members) <= 1)
 			continue;
 
-		/*
-		 * No point in searching if rel not mentioned in eclass (but we can't
-		 * tell that for a child rel).
-		 */
-		if (!is_child_rel &&
-			!bms_is_subset(rel->relids, cur_ec->ec_relids))
-			continue;
-
 		/*
 		 * Scan members, looking for a match to the target column.  Note that
 		 * child EC members are considered, but only when they belong to the
@@ -2366,11 +2436,18 @@ bool
 have_relevant_eclass_joinclause(PlannerInfo *root,
 								RelOptInfo *rel1, RelOptInfo *rel2)
 {
-	ListCell   *lc1;
+	Bitmapset  *matching_ecs;
+	int			i;
 
-	foreach(lc1, root->eq_classes)
+	/* Examine only eclasses mentioning both rel1 and rel2 */
+	matching_ecs = get_common_eclass_indexes(root, rel1->relids,
+											 rel2->relids);
+
+	i = -1;
+	while ((i = bms_next_member(matching_ecs, i)) >= 0)
 	{
-		EquivalenceClass *ec = (EquivalenceClass *) lfirst(lc1);
+		EquivalenceClass *ec = (EquivalenceClass *) list_nth(root->eq_classes,
+															 i);
 
 		/*
 		 * Won't generate joinclauses if single-member (this test covers the
@@ -2398,6 +2475,10 @@ have_relevant_eclass_joinclause(PlannerInfo *root,
 		 * b.y and a.x = 42", it is worth considering a join between a and b,
 		 * since the join result is likely to be small even though it'll end
 		 * up being an unqualified nestloop.
+		 *
+		 * The bms_overlap tests here are normally redundant, given the work
+		 * done by get_common_eclass_indexes, but we keep them in case either
+		 * rel's eclass_indexes contains extra entries.
 		 */
 		if (bms_overlap(rel1->relids, ec->ec_relids) &&
 			bms_overlap(rel2->relids, ec->ec_relids))
@@ -2419,11 +2500,17 @@ have_relevant_eclass_joinclause(PlannerInfo *root,
 bool
 has_relevant_eclass_joinclause(PlannerInfo *root, RelOptInfo *rel1)
 {
-	ListCell   *lc1;
+	Bitmapset  *matched_ecs;
+	int			i;
 
-	foreach(lc1, root->eq_classes)
+	/* Examine only eclasses mentioning rel1 */
+	matched_ecs = get_eclass_indexes_for_relids(root, rel1->relids);
+
+	i = -1;
+	while ((i = bms_next_member(matched_ecs, i)) >= 0)
 	{
-		EquivalenceClass *ec = (EquivalenceClass *) lfirst(lc1);
+		EquivalenceClass *ec = (EquivalenceClass *) list_nth(root->eq_classes,
+															 i);
 
 		/*
 		 * Won't generate joinclauses if single-member (this test covers the
@@ -2570,3 +2657,54 @@ is_redundant_with_indexclauses(RestrictInfo *rinfo, List *indexclauses)
 
 	return false;
 }
+
+/*
+ * get_eclass_indexes_for_relids
+ *		Build and return a Bitmapset containing the indexes into root's
+ *		eq_classes list for all eclasses that mention any of these relids
+ */
+static Bitmapset *
+get_eclass_indexes_for_relids(PlannerInfo *root, Relids relids)
+{
+	Bitmapset  *ec_indexes = NULL;
+	int			i = -1;
+
+	/* Should be OK to rely on eclass_indexes */
+	Assert(root->ec_merging_done);
+
+	while ((i = bms_next_member(relids, i)) > 0)
+	{
+		RelOptInfo *rel = root->simple_rel_array[i];
+
+		ec_indexes = bms_add_members(ec_indexes, rel->eclass_indexes);
+	}
+	return ec_indexes;
+}
+
+/*
+ * get_common_eclass_indexes
+ *		Build and return a Bitmapset containing the indexes into root's
+ *		eq_classes list for all eclasses that mention rels in both
+ *		relids1 and relids2.
+ */
+static Bitmapset *
+get_common_eclass_indexes(PlannerInfo *root, Relids relids1, Relids relids2)
+{
+	Bitmapset  *rel1ecs;
+	Bitmapset  *rel2ecs;
+	int			relid;
+
+	rel1ecs = get_eclass_indexes_for_relids(root, relids1);
+
+	/*
+	 * We can get away with just using the relation's eclass_indexes directly
+	 * when relids2 is a singleton set.
+	 */
+	if (bms_get_singleton_member(relids2, &relid))
+		rel2ecs = root->simple_rel_array[relid]->eclass_indexes;
+	else
+		rel2ecs = get_eclass_indexes_for_relids(root, relids2);
+
+	/* Calculate and return the common EC indexes, recycling the left input. */
+	return bms_int_members(rel1ecs, rel2ecs);
+}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index 202a4b9db8..2f4fea241a 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -48,9 +48,7 @@ static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
  *	  entry if there's not one already.
  *
  * Note that this function must not be used until after we have completed
- * merging EquivalenceClasses.  (We don't try to enforce that here; instead,
- * equivclass.c will complain if a merge occurs after root->canon_pathkeys
- * has become nonempty.)
+ * merging EquivalenceClasses.
  */
 PathKey *
 make_canonical_pathkey(PlannerInfo *root,
@@ -61,6 +59,10 @@ make_canonical_pathkey(PlannerInfo *root,
 	ListCell   *lc;
 	MemoryContext oldcontext;
 
+	/* Can't make canonical pathkeys if the set of ECs might still change */
+	if (!root->ec_merging_done)
+		elog(ERROR, "too soon to build canonical pathkeys");
+
 	/* The passed eclass might be non-canonical, so chase up to the top */
 	while (eclass->ec_merged)
 		eclass = eclass->ec_merged;
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index 2dbf1db844..df3f8c2544 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -139,6 +139,12 @@ query_planner(PlannerInfo *root,
 				/* Select cheapest path (pretty easy in this case...) */
 				set_cheapest(final_rel);
 
+				/*
+				 * We don't need to run generate_base_implied_equalities, but
+				 * we do need to pretend that EC merging is complete.
+				 */
+				root->ec_merging_done = true;
+
 				/*
 				 * We still are required to call qp_callback, in case it's
 				 * something like "SELECT 2+2 ORDER BY 1".
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index ca3b7f29e1..36fefd96a4 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -618,6 +618,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	root->cte_plan_ids = NIL;
 	root->multiexpr_params = NIL;
 	root->eq_classes = NIL;
+	root->ec_merging_done = false;
 	root->append_rel_list = NIL;
 	root->rowMarks = NIL;
 	memset(root->upper_rels, 0, sizeof(root->upper_rels));
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index e20bee0b33..4fbc03fe54 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -886,6 +886,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	subroot->cte_plan_ids = NIL;
 	subroot->multiexpr_params = NIL;
 	subroot->eq_classes = NIL;
+	subroot->ec_merging_done = false;
 	subroot->append_rel_list = NIL;
 	subroot->rowMarks = NIL;
 	memset(subroot->upper_rels, 0, sizeof(subroot->upper_rels));
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index f5f934ab5c..5a11c1235c 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -120,6 +120,15 @@ plan_set_operations(PlannerInfo *root)
 	Assert(parse->windowClause == NIL);
 	Assert(parse->distinctClause == NIL);
 
+	/*
+	 * In the outer query level, we won't have any true equivalences to deal
+	 * with; but we do want to be able to make pathkeys, which will require
+	 * single-member EquivalenceClasses.  Indicate that EC merging is complete
+	 * so that pathkeys.c won't complain.
+	 */
+	Assert(root->eq_classes == NIL);
+	root->ec_merging_done = true;
+
 	/*
 	 * We'll need to build RelOptInfos for each of the leaf subqueries, which
 	 * are RTE_SUBQUERY rangetable entries in this Query.  Prepare the index
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 6054bd2b53..37d228ce5d 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -218,6 +218,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->pages = 0;
 	rel->tuples = 0;
 	rel->allvisfrac = 0;
+	rel->eclass_indexes = NULL;
 	rel->subroot = NULL;
 	rel->subplan_params = NIL;
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
@@ -629,6 +630,7 @@ build_join_rel(PlannerInfo *root,
 	joinrel->pages = 0;
 	joinrel->tuples = 0;
 	joinrel->allvisfrac = 0;
+	joinrel->eclass_indexes = NULL;
 	joinrel->subroot = NULL;
 	joinrel->subplan_params = NIL;
 	joinrel->rel_parallel_workers = -1;
@@ -808,6 +810,7 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
 	joinrel->pages = 0;
 	joinrel->tuples = 0;
 	joinrel->allvisfrac = 0;
+	joinrel->eclass_indexes = NULL;
 	joinrel->subroot = NULL;
 	joinrel->subplan_params = NIL;
 	joinrel->serverid = InvalidOid;
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 441e64eca9..e3c579ee44 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -265,6 +265,8 @@ struct PlannerInfo
 
 	List	   *eq_classes;		/* list of active EquivalenceClasses */
 
+	bool		ec_merging_done;	/* set true once ECs are canonical */
+
 	List	   *canon_pathkeys; /* list of "canonical" PathKeys */
 
 	List	   *left_join_clauses;	/* list of RestrictInfos for mergejoinable
@@ -505,6 +507,8 @@ typedef struct PartitionSchemeData *PartitionScheme;
  *		pages - number of disk pages in relation (zero if not a table)
  *		tuples - number of tuples in relation (not considering restrictions)
  *		allvisfrac - fraction of disk pages that are marked all-visible
+ *		eclass_indexes - EquivalenceClasses that mention this rel (filled
+ *						 only after EC merging is complete)
  *		subroot - PlannerInfo for subquery (NULL if it's not a subquery)
  *		subplan_params - list of PlannerParamItems to be passed to subquery
  *
@@ -678,6 +682,8 @@ typedef struct RelOptInfo
 	BlockNumber pages;			/* size estimates derived from pg_class */
 	double		tuples;
 	double		allvisfrac;
+	Bitmapset  *eclass_indexes; /* Indexes in PlannerInfo's eq_classes list of
+								 * ECs that mention this rel */
 	PlannerInfo *subroot;		/* if subquery */
 	List	   *subplan_params; /* if subquery */
 	int			rel_parallel_workers;	/* wanted number of parallel workers */
-- 
2.17.1

