* restriction_is_constant_false()
* update_child_rel_info()
* build_child_join_sjinfo()
+ * get_matching_part_pairs()
+ * compute_partition_bounds()
*
*
* Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
if (rel->part_scheme)
rel->partitioned_child_rels =
list_concat(rel->partitioned_child_rels,
- list_copy(childrel->partitioned_child_rels));
+ childrel->partitioned_child_rels);
/*
* Child is live, so add it to the live_childrels list for use below.
* once we know the final targetlist (see grouping_planner).
*/
if (lev < levels_needed)
- generate_gather_paths(root, rel, false);
+ generate_useful_gather_paths(root, rel, false);
/* Find and save the cheapest paths for this rel */
set_cheapest(rel);
* to each initial rel they don't already include but have a join
* clause or restriction with.
*/
+ List *other_rels_list;
ListCell *other_rels;
if (level == 2) /* consider remaining initial rels */
- other_rels = lnext(r);
+ {
+ other_rels_list = joinrels[level - 1];
+ other_rels = lnext(other_rels_list, r);
+ }
else /* consider all initial rels */
- other_rels = list_head(joinrels[1]);
+ {
+ other_rels_list = joinrels[1];
+ other_rels = list_head(other_rels_list);
+ }
make_rels_by_clause_joins(root,
old_rel,
+ other_rels_list,
other_rels);
}
else
*/
make_rels_by_clauseless_joins(root,
old_rel,
- list_head(joinrels[1]));
+ joinrels[1]);
}
}
foreach(r, joinrels[k])
{
RelOptInfo *old_rel = (RelOptInfo *) lfirst(r);
+ List *other_rels_list;
ListCell *other_rels;
ListCell *r2;
continue;
if (k == other_level)
- other_rels = lnext(r); /* only consider remaining rels */
+ {
+ /* only consider remaining rels */
+ other_rels_list = joinrels[k];
+ other_rels = lnext(other_rels_list, r);
+ }
else
- other_rels = list_head(joinrels[other_level]);
+ {
+ other_rels_list = joinrels[other_level];
+ other_rels = list_head(other_rels_list);
+ }
- for_each_cell(r2, other_rels)
+ for_each_cell(r2, other_rels_list, other_rels)
{
RelOptInfo *new_rel = (RelOptInfo *) lfirst(r2);
make_rels_by_clauseless_joins(root,
old_rel,
- list_head(joinrels[1]));
+ joinrels[1]);
}
/*----------
* automatically ensures that each new joinrel is only added to the list once.
*
* 'old_rel' is the relation entry for the relation to be joined
- * 'other_rels': the first cell in a linked list containing the other
+ * 'other_rels_list': a list containing the other
* rels to be considered for joining
+ * 'other_rels': the first cell to be considered
*
* Currently, this is only used with initial rels in other_rels, but it
* will work for joining to joinrels too.
static void
make_rels_by_clause_joins(PlannerInfo *root,
RelOptInfo *old_rel,
+ List *other_rels_list,
ListCell *other_rels)
{
ListCell *l;
- for_each_cell(l, other_rels)
+ for_each_cell(l, other_rels_list, other_rels)
{
RelOptInfo *other_rel = (RelOptInfo *) lfirst(l);
* The join rels are returned in root->join_rel_level[join_cur_level].
*
* 'old_rel' is the relation entry for the relation to be joined
- * 'other_rels': the first cell of a linked list containing the
- * other rels to be considered for joining
+ * 'other_rels': a list containing the other rels to be considered for joining
*
* Currently, this is only used with initial rels in other_rels, but it would
* work for joining to joinrels too.
static void
make_rels_by_clauseless_joins(PlannerInfo *root,
RelOptInfo *old_rel,
- ListCell *other_rels)
+ List *other_rels)
{
ListCell *l;
- for_each_cell(l, other_rels)
+ foreach(l, other_rels)
{
RelOptInfo *other_rel = (RelOptInfo *) lfirst(l);
}
/*
+ * get_matching_part_pairs
+ * Generate pairs of partitions to be joined from inputs
+ */
+static void
+get_matching_part_pairs(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, RelOptInfo *rel2,
+ List **parts1, List **parts2)
+{
+ bool rel1_is_simple = IS_SIMPLE_REL(rel1);
+ bool rel2_is_simple = IS_SIMPLE_REL(rel2);
+ int cnt_parts;
+
+ *parts1 = NIL;
+ *parts2 = NIL;
+
+ for (cnt_parts = 0; cnt_parts < joinrel->nparts; cnt_parts++)
+ {
+ RelOptInfo *child_joinrel = joinrel->part_rels[cnt_parts];
+ RelOptInfo *child_rel1;
+ RelOptInfo *child_rel2;
+ Relids child_relids1;
+ Relids child_relids2;
+
+ /*
+ * If this segment of the join is empty, it means that this segment
+ * was ignored when previously creating child-join paths for it in
+ * try_partitionwise_join() as it would not contribute to the join
+ * result, due to one or both inputs being empty; add NULL to each of
+ * the given lists so that this segment will be ignored again in that
+ * function.
+ */
+ if (!child_joinrel)
+ {
+ *parts1 = lappend(*parts1, NULL);
+ *parts2 = lappend(*parts2, NULL);
+ continue;
+ }
+
+ /*
+ * Get a relids set of partition(s) involved in this join segment that
+ * are from the rel1 side.
+ */
+ child_relids1 = bms_intersect(child_joinrel->relids,
+ rel1->all_partrels);
+ Assert(bms_num_members(child_relids1) == bms_num_members(rel1->relids));
+
+ /*
+ * Get a child rel for rel1 with the relids. Note that we should have
+ * the child rel even if rel1 is a join rel, because in that case the
+ * partitions specified in the relids would have matching/overlapping
+ * boundaries, so the specified partitions should be considered as
+ * ones to be joined when planning partitionwise joins of rel1,
+ * meaning that the child rel would have been built by the time we get
+ * here.
+ */
+ if (rel1_is_simple)
+ {
+ int varno = bms_singleton_member(child_relids1);
+
+ child_rel1 = find_base_rel(root, varno);
+ }
+ else
+ child_rel1 = find_join_rel(root, child_relids1);
+ Assert(child_rel1);
+
+ /*
+ * Get a relids set of partition(s) involved in this join segment that
+ * are from the rel2 side.
+ */
+ child_relids2 = bms_intersect(child_joinrel->relids,
+ rel2->all_partrels);
+ Assert(bms_num_members(child_relids2) == bms_num_members(rel2->relids));
+
+ /*
+ * Get a child rel for rel2 with the relids. See above comments.
+ */
+ if (rel2_is_simple)
+ {
+ int varno = bms_singleton_member(child_relids2);
+
+ child_rel2 = find_base_rel(root, varno);
+ }
+ else
+ child_rel2 = find_join_rel(root, child_relids2);
+ Assert(child_rel2);
+
+ /*
+ * The join of rel1 and rel2 is legal, so is the join of the child
+ * rels obtained above; add them to the given lists as a join pair
+ * producing this join segment.
+ */
+ *parts1 = lappend(*parts1, child_rel1);
+ *parts2 = lappend(*parts2, child_rel2);
+ }
+}
+
+
+/*
+ * compute_partition_bounds
+ * Compute the partition bounds for a join rel from those for inputs
+ */
+static void
+compute_partition_bounds(PlannerInfo *root, RelOptInfo *rel1,
+ RelOptInfo *rel2, RelOptInfo *joinrel,
+ SpecialJoinInfo *parent_sjinfo,
+ List **parts1, List **parts2)
+{
+ /*
+ * If we don't have the partition bounds for the join rel yet, try to
+ * compute those along with pairs of partitions to be joined.
+ */
+ if (joinrel->nparts == -1)
+ {
+ PartitionScheme part_scheme = joinrel->part_scheme;
+ PartitionBoundInfo boundinfo = NULL;
+ int nparts = 0;
+
+ Assert(joinrel->boundinfo == NULL);
+ Assert(joinrel->part_rels == NULL);
+
+ /*
+ * See if the partition bounds for inputs are exactly the same, in
+ * which case we don't need to work hard: the join rel have the same
+ * partition bounds as inputs, and the partitions with the same
+ * cardinal positions form the pairs.
+ *
+ * Note: even in cases where one or both inputs have merged bounds, it
+ * would be possible for both the bounds to be exactly the same, but
+ * it seems unlikely to be worth the cycles to check.
+ */
+ if (!rel1->partbounds_merged &&
+ !rel2->partbounds_merged &&
+ rel1->nparts == rel2->nparts &&
+ partition_bounds_equal(part_scheme->partnatts,
+ part_scheme->parttyplen,
+ part_scheme->parttypbyval,
+ rel1->boundinfo, rel2->boundinfo))
+ {
+ boundinfo = rel1->boundinfo;
+ nparts = rel1->nparts;
+ }
+ else
+ {
+ /* Try merging the partition bounds for inputs. */
+ boundinfo = partition_bounds_merge(part_scheme->partnatts,
+ part_scheme->partsupfunc,
+ part_scheme->partcollation,
+ rel1, rel2,
+ parent_sjinfo->jointype,
+ parts1, parts2);
+ if (boundinfo == NULL)
+ {
+ joinrel->nparts = 0;
+ return;
+ }
+ nparts = list_length(*parts1);
+ joinrel->partbounds_merged = true;
+ }
+
+ Assert(nparts > 0);
+ joinrel->boundinfo = boundinfo;
+ joinrel->nparts = nparts;
+ joinrel->part_rels =
+ (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * nparts);
+ }
+ else
+ {
+ Assert(joinrel->nparts > 0);
+ Assert(joinrel->boundinfo);
+ Assert(joinrel->part_rels);
+
+ /*
+ * If the join rel's partbounds_merged flag is true, it means inputs
+ * are not guaranteed to have the same partition bounds, therefore we
+ * can't assume that the partitions at the same cardinal positions
+ * form the pairs; let get_matching_part_pairs() generate the pairs.
+ * Otherwise, nothing to do since we can assume that.
+ */
+ if (joinrel->partbounds_merged)
+ {
+ get_matching_part_pairs(root, joinrel, rel1, rel2,
+ parts1, parts2);
+ Assert(list_length(*parts1) == joinrel->nparts);
+ Assert(list_length(*parts2) == joinrel->nparts);
+ }
+ }
+}
+
+
+/*
* Assess whether join between given two partitioned relations can be broken
* down into joins between matching partitions; a technique called
* "partitionwise join"
{
bool rel1_is_simple = IS_SIMPLE_REL(rel1);
bool rel2_is_simple = IS_SIMPLE_REL(rel2);
- int nparts;
+ List *parts1 = NIL;
+ List *parts2 = NIL;
+ ListCell *lcr1 = NULL;
+ ListCell *lcr2 = NULL;
int cnt_parts;
/* Guard against stack overflow due to overly deep partition hierarchy. */
check_stack_depth();
/* Nothing to do, if the join relation is not partitioned. */
- if (!IS_PARTITIONED_REL(joinrel))
+ if (joinrel->part_scheme == NULL || joinrel->nparts == 0)
return;
/* The join relation should have consider_partitionwise_join set. */
Assert(joinrel->consider_partitionwise_join);
/*
- * Since this join relation is partitioned, all the base relations
- * participating in this join must be partitioned and so are all the
- * intermediate join relations.
+ * We can not perform partitionwise join if either of the joining
+ * relations is not partitioned.
*/
- Assert(IS_PARTITIONED_REL(rel1) && IS_PARTITIONED_REL(rel2));
+ if (!IS_PARTITIONED_REL(rel1) || !IS_PARTITIONED_REL(rel2))
+ return;
+
Assert(REL_HAS_ALL_PART_PROPS(rel1) && REL_HAS_ALL_PART_PROPS(rel2));
/* The joining relations should have consider_partitionwise_join set. */
Assert(joinrel->part_scheme == rel1->part_scheme &&
joinrel->part_scheme == rel2->part_scheme);
- /*
- * Since we allow partitionwise join only when the partition bounds of the
- * joining relations exactly match, the partition bounds of the join
- * should match those of the joining relations.
- */
- Assert(partition_bounds_equal(joinrel->part_scheme->partnatts,
- joinrel->part_scheme->parttyplen,
- joinrel->part_scheme->parttypbyval,
- joinrel->boundinfo, rel1->boundinfo));
- Assert(partition_bounds_equal(joinrel->part_scheme->partnatts,
- joinrel->part_scheme->parttyplen,
- joinrel->part_scheme->parttypbyval,
- joinrel->boundinfo, rel2->boundinfo));
+ Assert(!(joinrel->partbounds_merged && (joinrel->nparts <= 0)));
+
+ compute_partition_bounds(root, rel1, rel2, joinrel, parent_sjinfo,
+ &parts1, &parts2);
- nparts = joinrel->nparts;
+ if (joinrel->partbounds_merged)
+ {
+ lcr1 = list_head(parts1);
+ lcr2 = list_head(parts2);
+ }
/*
* Create child-join relations for this partitioned join, if those don't
* exist. Add paths to child-joins for a pair of child relations
* corresponding to the given pair of parent relations.
*/
- for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++)
+ for (cnt_parts = 0; cnt_parts < joinrel->nparts; cnt_parts++)
{
- RelOptInfo *child_rel1 = rel1->part_rels[cnt_parts];
- RelOptInfo *child_rel2 = rel2->part_rels[cnt_parts];
- bool rel1_empty = (child_rel1 == NULL ||
- IS_DUMMY_REL(child_rel1));
- bool rel2_empty = (child_rel2 == NULL ||
- IS_DUMMY_REL(child_rel2));
+ RelOptInfo *child_rel1;
+ RelOptInfo *child_rel2;
+ bool rel1_empty;
+ bool rel2_empty;
SpecialJoinInfo *child_sjinfo;
List *child_restrictlist;
RelOptInfo *child_joinrel;
AppendRelInfo **appinfos;
int nappinfos;
+ if (joinrel->partbounds_merged)
+ {
+ child_rel1 = lfirst_node(RelOptInfo, lcr1);
+ child_rel2 = lfirst_node(RelOptInfo, lcr2);
+ lcr1 = lnext(parts1, lcr1);
+ lcr2 = lnext(parts2, lcr2);
+ }
+ else
+ {
+ child_rel1 = rel1->part_rels[cnt_parts];
+ child_rel2 = rel2->part_rels[cnt_parts];
+ }
+
+ rel1_empty = (child_rel1 == NULL || IS_DUMMY_REL(child_rel1));
+ rel2_empty = (child_rel2 == NULL || IS_DUMMY_REL(child_rel2));
+
/*
* Check for cases where we can prove that this segment of the join
* returns no rows, due to one or both inputs being empty (including
child_sjinfo,
child_sjinfo->jointype);
joinrel->part_rels[cnt_parts] = child_joinrel;
+ joinrel->all_partrels = bms_add_members(joinrel->all_partrels,
+ child_joinrel->relids);
}
Assert(bms_equal(child_joinrel->relids, child_joinrelids));