#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 EquivalenceMember *add_child_eq_member(PlannerInfo *root,
+ EquivalenceClass *ec,
+ int ec_index, Expr *expr,
+ Relids relids,
+ JoinDomain *jdomain,
+ EquivalenceMember *parent_em,
+ Oid datatype,
+ Index child_relid);
static void generate_base_implied_equalities_const(PlannerInfo *root,
EquivalenceClass *ec);
static void generate_base_implied_equalities_no_const(PlannerInfo *root,
if (!equal(opfamilies, cur_ec->ec_opfamilies))
continue;
+ /* We don't expect any children yet */
+ Assert(cur_ec->ec_childmembers == NULL);
+
foreach(lc2, cur_ec->ec_members)
{
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
{
/* 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);
{
/* 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);
ec->ec_opfamilies = opfamilies;
ec->ec_collation = collation;
+ ec->ec_childmembers_size = 0;
ec->ec_members = NIL;
+ ec->ec_childmembers = NULL;
ec->ec_sources = list_make1(restrictinfo);
ec->ec_derives_list = NIL;
ec->ec_derives_hash = NULL;
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);
}
/*
- * 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'
+ * is NULL, the result will be a parent member, otherwise a child member.
*/
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);
ec->ec_has_const = true;
/* it can't affect ec_relids */
}
- else if (!parent) /* child members don't add to ec_relids */
+
+ return em;
+}
+
+/*
+ * add_eq_member - build a new non-child EquivalenceMember and add it to 'ec'.
+ */
+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);
+
+ /* add to the members list */
+ ec->ec_members = lappend(ec->ec_members, em);
+
+ /* record the relids for parent members */
+ ec->ec_relids = bms_add_members(ec->ec_relids, relids);
+
+ return em;
+}
+
+/*
+ * add_child_eq_member
+ * Create an em_is_child=true EquivalenceMember and add it to 'ec'.
+ *
+ * 'root' is the PlannerInfo that 'ec' belongs to.
+ * 'ec' is the EquivalenceClass to add the child member to.
+ * 'ec_index' the index of 'ec' within root->eq_classes, or -1 if maintaining
+ * the RelOptInfo.eclass_indexes isn't needed.
+ * 'expr' is the em_expr for the new member.
+ * 'relids' is the 'em_relids' for the new member.
+ * 'jdomain' is the 'em_jdomain' for the new member.
+ * 'parent_em' is the parent member of the child to create.
+ * 'datatype' is the em_datatype of the new member.
+ * 'child_relid' defines which element of ec_childmembers to add this member
+ * to. This is generally a RELOPT_OTHER_MEMBER_REL, but for set operations
+ * can be a RELOPT_BASEREL representing the set-op children.
+ */
+static EquivalenceMember *
+add_child_eq_member(PlannerInfo *root, EquivalenceClass *ec, int ec_index,
+ Expr *expr, Relids relids, JoinDomain *jdomain,
+ EquivalenceMember *parent_em, Oid datatype,
+ Index child_relid)
+{
+ EquivalenceMember *em;
+
+ Assert(parent_em != NULL);
+
+ /*
+ * Allocate the array to store child members; an array of Lists indexed by
+ * relid, or expand the existing one, if necessary.
+ */
+ if (unlikely(ec->ec_childmembers_size < root->simple_rel_array_size))
{
- ec->ec_relids = bms_add_members(ec->ec_relids, relids);
+ if (ec->ec_childmembers == NULL)
+ ec->ec_childmembers = palloc0_array(List *, root->simple_rel_array_size);
+ else
+ ec->ec_childmembers = repalloc0_array(ec->ec_childmembers, List *,
+ ec->ec_childmembers_size,
+ root->simple_rel_array_size);
+
+ ec->ec_childmembers_size = root->simple_rel_array_size;
+ }
+
+ em = make_eq_member(ec, expr, relids, jdomain, parent_em, datatype);
+
+ /* add member to the ec_childmembers List for the given child_relid */
+ ec->ec_childmembers[child_relid] = lappend(ec->ec_childmembers[child_relid], em);
+
+ /* Record this EC index for the child rel */
+ if (ec_index >= 0)
+ {
+ RelOptInfo *child_rel = root->simple_rel_array[child_relid];
+
+ child_rel->eclass_indexes =
+ bms_add_member(child_rel->eclass_indexes, ec_index);
}
- ec->ec_members = lappend(ec->ec_members, em);
return em;
}
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
if (!equal(opfamilies, cur_ec->ec_opfamilies))
continue;
- foreach(lc2, cur_ec->ec_members)
+ setup_eclass_member_iterator(&it, 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.
*/
newec = makeNode(EquivalenceClass);
newec->ec_opfamilies = list_copy(opfamilies);
newec->ec_collation = collation;
+ newec->ec_childmembers_size = 0;
newec->ec_members = NIL;
+ newec->ec_childmembers = NULL;
newec->ec_sources = NIL;
newec->ec_derives_list = NIL;
newec->ec_derives_hash = NULL;
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
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(&it, ec, relids);
+ while ((em = eclass_member_iterator_next(&it)) != NULL)
{
- EquivalenceMember *em = (EquivalenceMember *) lfirst(lc);
Expr *emexpr;
/*
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
PVC_INCLUDE_PLACEHOLDERS |
PVC_INCLUDE_CONVERTROWTYPES);
- foreach(lc, ec->ec_members)
+ setup_eclass_member_iterator(&it, ec, relids);
+ while ((em = eclass_member_iterator_next(&it)) != NULL)
{
- EquivalenceMember *em = (EquivalenceMember *) lfirst(lc);
List *emvars;
ListCell *lc2;
return;
}
+ /* We don't expect any children yet */
+ Assert(ec->ec_childmembers == NULL);
+
/*
* Find the constant member to use. We prefer an actual constant to
* pseudo-constants (such as Params), because the constraint exclusion
Oid eq_op;
RestrictInfo *rinfo;
- Assert(!cur_em->em_is_child); /* no children yet */
+ /* Child members should not exist in ec_members */
+ Assert(!cur_em->em_is_child);
if (cur_em == const_em)
continue;
eq_op = select_equality_operator(ec,
prev_ems = (EquivalenceMember **)
palloc0(root->simple_rel_array_size * sizeof(EquivalenceMember *));
+ /* We don't expect any children yet */
+ Assert(ec->ec_childmembers == NULL);
+
foreach(lc, ec->ec_members)
{
EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc);
int relid;
- Assert(!cur_em->em_is_child); /* no children yet */
+ /* Child members should not exist in ec_members */
+ Assert(!cur_em->em_is_child);
+
if (!bms_get_singleton_member(cur_em->em_relids, &relid))
continue;
Assert(relid < root->simple_rel_array_size);
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
* 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(&it, 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
Oid best_eq_op = InvalidOid;
int best_score = -1;
RestrictInfo *rinfo;
+ ListCell *lc1;
foreach(lc1, outer_members)
{
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)
foreach(lc1, new_members)
{
- EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc1);
+ cur_em = (EquivalenceMember *) lfirst(lc1);
if (prev_em != NULL)
{
bool match;
ListCell *lc2;
+ /* We don't expect any children yet */
+ Assert(cur_ec->ec_childmembers == NULL);
+
/* Ignore EC unless it contains pseudoconstants */
if (!cur_ec->ec_has_const)
continue;
{
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);
if (equal(outervar, cur_em->em_expr))
{
match = true;
ListCell *lc2;
int coal_idx = -1;
+ /* We don't expect any children yet */
+ Assert(cur_ec->ec_childmembers == NULL);
+
/* Ignore EC unless it contains pseudoconstants */
if (!cur_ec->ec_has_const)
continue;
foreach(lc2, cur_ec->ec_members)
{
coal_em = (EquivalenceMember *) lfirst(lc2);
- Assert(!coal_em->em_is_child); /* no children yet */
+
+ /* Child members should not exist in ec_members */
+ Assert(!coal_em->em_is_child);
if (IsA(coal_em->em_expr, CoalesceExpr))
{
CoalesceExpr *cexpr = (CoalesceExpr *) coal_em->em_expr;
{
EquivalenceClass *ec = (EquivalenceClass *) lfirst(lc);
+ /*
+ * We don't expect any EC child members to exist at this point. Ensure
+ * that's the case, otherwise, we might be getting asked to do
+ * something this function hasn't been coded for.
+ */
+ Assert(ec->ec_childmembers == NULL);
+
/* Need do anything only for a multi-member, no-const EC. */
if (list_length(ec->ec_members) > 1 && !ec->ec_has_const)
{
!list_member_oid(ec->ec_opfamilies, opfamily))
continue;
+ /* Ignore children here */
foreach(lc2, ec->ec_members)
{
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))
/* Never match to a volatile EC */
if (ec->ec_has_volatile)
continue;
- /* It's okay to consider "broken" ECs here, see exprs_known_equal */
+ /*
+ * It's okay to consider "broken" ECs here, see exprs_known_equal.
+ * Ignore children here.
+ */
foreach(lc2, ec->ec_members)
{
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;
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
/* 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_node(EquivalenceMember, cur_em, cur_ec->ec_members)
{
- EquivalenceMember *cur_em = (EquivalenceMember *) list_nth(cur_ec->ec_members, pos);
-
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
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);
-
- /* Record this EC index for the child rel */
- child_rel->eclass_indexes = bms_add_member(child_rel->eclass_indexes, i);
+ add_child_eq_member(root,
+ cur_ec,
+ i,
+ child_expr,
+ new_relids,
+ cur_em->em_jdomain,
+ cur_em,
+ cur_em->em_datatype,
+ child_rel->relid);
}
}
}
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
/* 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_node(EquivalenceMember, cur_em, cur_ec->ec_members)
{
- EquivalenceMember *cur_em = (EquivalenceMember *) list_nth(cur_ec->ec_members, pos);
-
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,
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);
+ /*
+ * Add new child member to the EquivalenceClass. Because this
+ * is a RELOPT_OTHER_JOINREL which has multiple component
+ * relids, there is no ideal place to store these members in
+ * the class. Ordinarily, child members are stored in the
+ * ec_childmembers[] array element corresponding to their
+ * relid, however, here we have multiple component relids, so
+ * there's no single ec_childmembers[] array element to store
+ * this member. So that we still correctly find this member
+ * in loops iterating over an EquivalenceMemberIterator, we
+ * opt to store the member in the ec_childmembers array in
+ * only the first component relid slot of the array. This
+ * allows the member to be found, providing callers of
+ * setup_eclass_member_iterator() specify all the component
+ * relids for the RELOPT_OTHER_JOINREL, which they do. If we
+ * opted to store the member in each ec_childmembers[] element
+ * for all the component relids, then that would just result
+ * in eclass_member_iterator_next() finding the member
+ * multiple times, which is a waste of effort.
+ */
+ add_child_eq_member(root,
+ cur_ec,
+ -1,
+ child_expr,
+ new_relids,
+ cur_em->em_jdomain,
+ cur_em,
+ cur_em->em_datatype,
+ bms_next_member(child_joinrel->relids, -1));
}
}
}
* We can safely pass the parent member as the first member in the
* ec_members list as this is added first in generate_union_paths,
* likewise, the JoinDomain can be that of the initial member of the
- * Pathkey's EquivalenceClass.
+ * Pathkey's EquivalenceClass. We pass -1 for ec_index since we
+ * maintain the eclass_indexes for the child_rel after the loop.
*/
- add_eq_member(pk->pk_eclass,
- tle->expr,
- child_rel->relids,
- parent_em->em_jdomain,
- parent_em,
- exprType((Node *) tle->expr));
+ add_child_eq_member(root,
+ pk->pk_eclass,
+ -1,
+ tle->expr,
+ child_rel->relids,
+ parent_em->em_jdomain,
+ parent_em,
+ exprType((Node *) tle->expr),
+ child_rel->relid);
lc2 = lnext(setop_pathkeys, lc2);
}
list_length(root->eq_classes) - 1);
}
+/*
+ * setup_eclass_member_iterator
+ * Setup an EquivalenceMemberIterator 'it' to iterate over all parent
+ * EquivalenceMembers and child members belonging to the given 'ec'.
+ *
+ * This iterator returns:
+ * - All parent members stored directly in ec_members for 'ec', and;
+ * - Any child member added to the given ec by add_child_eq_member() where
+ * the child_relid specified in the add_child_eq_member() call is a member
+ * of the 'child_relids' parameter.
+ *
+ * Note:
+ * The given 'child_relids' must remain allocated and not be changed for the
+ * lifetime of the iterator.
+ *
+ * Parameters:
+ * 'it' is a pointer to the iterator to set up. Normally stack allocated.
+ * 'ec' is the EquivalenceClass from which to iterate members for.
+ * 'child_relids' is the relids to return child members for.
+ */
+void
+setup_eclass_member_iterator(EquivalenceMemberIterator *it,
+ EquivalenceClass *ec, Relids child_relids)
+{
+ it->ec = ec;
+ /* no need to set this if the class has no child members array set */
+ it->child_relids = ec->ec_childmembers != NULL ? child_relids : NULL;
+ it->current_relid = -1;
+ it->current_list = ec->ec_members;
+ it->current_cell = list_head(it->current_list);
+}
+
+/*
+ * eclass_member_iterator_next
+ * Get the next EquivalenceMember from the EquivalenceMemberIterator 'it',
+ * as setup by setup_eclass_member_iterator(). NULL is returned if there
+ * are no members left, after which callers must not call
+ * eclass_member_iterator_next() again for the given iterator.
+ */
+EquivalenceMember *
+eclass_member_iterator_next(EquivalenceMemberIterator *it)
+{
+ while (it->current_list != NULL)
+ {
+ while (it->current_cell != NULL)
+ {
+ EquivalenceMember *em;
+
+ nextcell:
+ em = lfirst_node(EquivalenceMember, it->current_cell);
+ it->current_cell = lnext(it->current_list, it->current_cell);
+ return em;
+ }
+
+ /* Search for the next list to return members from */
+ while ((it->current_relid = bms_next_member(it->child_relids, it->current_relid)) > 0)
+ {
+ /*
+ * Be paranoid in case we're given relids above what we've sized
+ * the ec_childmembers array to.
+ */
+ if (it->current_relid >= it->ec->ec_childmembers_size)
+ return NULL;
+
+ it->current_list = it->ec->ec_childmembers[it->current_relid];
+
+ /* If there are members in this list, use it. */
+ if (it->current_list != NIL)
+ {
+ /* point current_cell to the head of this list */
+ it->current_cell = list_head(it->current_list);
+ goto nextcell;
+ }
+ }
+ return NULL;
+ }
+
+ return NULL;
+}
/*
* generate_implied_equalities_for_column
while ((i = bms_next_member(rel->eclass_indexes, i)) >= 0)
{
EquivalenceClass *cur_ec = (EquivalenceClass *) list_nth(root->eq_classes, i);
+ EquivalenceMemberIterator it;
EquivalenceMember *cur_em;
ListCell *lc2;
* 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(&it, 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;
}
if (!cur_em)
/*
* Found our match. Scan the other EC members and attempt to generate
- * joinclauses.
+ * joinclauses. Ignore children here.
*/
foreach(lc2, cur_ec->ec_members)
{
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 ||
if (bms_is_subset(eclass->ec_relids, relids))
return false;
- /* To join, we need a member not in the given rel */
+ /*
+ * To join, we need a member not in the given rel. Ignore children here.
+ */
foreach(lc, eclass->ec_members)
{
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;
* In contrast, ec_sources holds equality clauses that appear directly in the
* query. These are typically few and do not require a hash table for lookup.
*
+ * 'ec_members' is a List of all !em_is_child EquivalenceMembers in the class.
+ * EquivalenceMembers for any RELOPT_OTHER_MEMBER_REL and RELOPT_OTHER_JOINREL
+ * relations are stored in the 'ec_childmembers' array in the index
+ * corresponding to the relid, or first component relid in the case of
+ * RELOPT_OTHER_JOINRELs. 'ec_childmembers' is NULL if the class has no child
+ * EquivalenceMembers.
+ *
+ * For code wishing to look at EquivalenceMembers, if only parent-level
+ * members are needed, then a simple foreach loop over ec_members is
+ * sufficient. When child members are also required, it is best to use the
+ * functionality provided by EquivalenceMemberIterator. This visits all
+ * parent members and only the relevant child members. The reason for this
+ * is that large numbers of child EquivalenceMembers can exist in queries to
+ * partitioned tables with many partitions. The functionality provided by
+ * EquivalenceMemberIterator allows efficient access to EquivalenceMembers
+ * which belong to specific child relids. See the header comments for
+ * EquivalenceMemberIterator below for further details.
+ *
* 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.
*
List *ec_opfamilies; /* btree operator family OIDs */
Oid ec_collation; /* collation, if datatypes are collatable */
+ int ec_childmembers_size; /* # elements in ec_childmembers */
List *ec_members; /* list of EquivalenceMembers */
+ List **ec_childmembers; /* array of Lists of child members */
List *ec_sources; /* list of generating RestrictInfos */
List *ec_derives_list; /* list of derived RestrictInfos */
struct derives_hash *ec_derives_hash; /* optional hash table for fast
* child when necessary to build a MergeAppend path for the whole appendrel
* tree. An em_is_child member has no impact on the properties of the EC as a
* whole; in particular the EC's ec_relids field does NOT include the child
- * relation. An em_is_child member should never be marked em_is_const nor
- * cause ec_has_const or ec_has_volatile to be set, either. Thus, em_is_child
- * members are not really full-fledged members of the EC, but just reflections
- * or doppelgangers of real members. Most operations on EquivalenceClasses
- * should ignore em_is_child members, and those that don't should test
- * em_relids to make sure they only consider relevant members.
+ * relation. em_is_child members aren't stored in the ec_members List of the
+ * EC and instead they're stored and indexed by the relids of the child
+ * relation they represent in ec_childmembers. An em_is_child member
+ * should never be marked em_is_const nor cause ec_has_const or
+ * ec_has_volatile to be set, either. Thus, em_is_child members are not
+ * really full-fledged members of the EC, but just reflections or
+ * doppelgangers of real members. Most operations on EquivalenceClasses
+ * should ignore em_is_child members by only inspecting members in the
+ * ec_members list. Callers that require inspecting child members should do
+ * so using an EquivalenceMemberIterator and should test em_relids to make
+ * sure they only consider relevant members.
*
* em_datatype is usually the same as exprType(em_expr), but can be
* different when dealing with a binary-compatible opfamily; in particular
struct EquivalenceMember *em_parent pg_node_attr(read_write_ignore);
} EquivalenceMember;
+/*
+ * EquivalenceMemberIterator
+ *
+ * EquivalenceMemberIterator allows efficient access to sets of
+ * EquivalenceMembers for callers which require access to child members.
+ * Because partitioning workloads can result in large numbers of child
+ * members, the child members are not stored in the EquivalenceClass's
+ * ec_members List. Instead, these are stored in the EquivalenceClass's
+ * ec_childmembers array of Lists. The functionality provided by
+ * EquivalenceMemberIterator aims to provide efficient access to parent
+ * members and child members belonging to specific child relids.
+ *
+ * Currently, there is only one way to initialize and iterate over an
+ * EquivalenceMemberIterator and that is via the setup_eclass_member_iterator
+ * and eclass_member_iterator_next functions. The iterator object is
+ * generally a local variable which is passed by address to
+ * setup_eclass_member_iterator. The calling function defines which
+ * EquivalenceClass the iterator should be looking at and which child
+ * relids to also return members for. child_relids can be passed as NULL, but
+ * the caller may as well just perform a foreach loop over ec_members as only
+ * parent-level members will be returned in that case.
+ *
+ * When calling the next function on an EquivalenceMemberIterator, all
+ * parent-level EquivalenceMembers are returned first, followed by all child
+ * members for the specified 'child_relids' for all child members which were
+ * indexed by any of the specified 'child_relids' in add_child_eq_member().
+ *
+ * Code using the iterator method of finding EquivalenceMembers will generally
+ * always want to ensure the returned member matches their search criteria
+ * rather than relying on the filtering to be done for them as all parent
+ * members are returned and for members belonging to RELOPT_OTHER_JOINREL
+ * rels, the member's em_relids may be a superset of the specified
+ * 'child_relids', which might not be what the caller wants.
+ *
+ * The most common way to use this iterator is as follows:
+ * -----
+ * EquivalenceMemberIterator it;
+ * EquivalenceMember *em;
+ *
+ * setup_eclass_member_iterator(&it, ec, child_relids);
+ * while ((em = eclass_member_iterator_next(&it)) != NULL)
+ * {
+ * ...
+ * }
+ * -----
+ * It is not valid to call eclass_member_iterator_next() after it has returned
+ * NULL for any given EquivalenceMemberIterator. Individual fields within
+ * the EquivalenceMemberIterator struct must not be accessed by callers.
+ */
+typedef struct
+{
+ EquivalenceClass *ec; /* The EquivalenceClass to iterate over */
+ int current_relid; /* Current relid position within 'relids'. -1
+ * when still looping over ec_members and -2
+ * at the end of iteration */
+ Relids child_relids; /* Relids of child relations of interest.
+ * Non-child rels are ignored */
+ ListCell *current_cell; /* Next cell to return within current_list */
+ List *current_list; /* Current list of members being returned */
+} EquivalenceMemberIterator;
+
/*
* PathKeys
*