1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-07 00:36:50 +03:00

Basic partition-wise join functionality.

Instead of joining two partitioned tables in their entirety we can, if
it is an equi-join on the partition keys, join the matching partitions
individually.  This involves teaching the planner about "other join"
rels, which are related to regular join rels in the same way that
other member rels are related to baserels.  This can use significantly
more CPU time and memory than regular join planning, because there may
now be a set of "other" rels not only for every base relation but also
for every join relation.  In most practical cases, this probably
shouldn't be a problem, because (1) it's probably unusual to join many
tables each with many partitions using the partition keys for all
joins and (2) if you do that scenario then you probably have a big
enough machine to handle the increased memory cost of planning and (3)
the resulting plan is highly likely to be better, so what you spend in
planning you'll make up on the execution side.  All the same, for now,
turn this feature off by default.

Currently, we can only perform joins between two tables whose
partitioning schemes are absolutely identical.  It would be nice to
cope with other scenarios, such as extra partitions on one side or the
other with no match on the other side, but that will have to wait for
a future patch.

Ashutosh Bapat, reviewed and tested by Rajkumar Raghuwanshi, Amit
Langote, Rafia Sabih, Thomas Munro, Dilip Kumar, Antonin Houska, Amit
Khandekar, and by me.  A few final adjustments by me.

Discussion: http://postgr.es/m/CAFjFpRfQ8GrQvzp3jA2wnLqrHmaXna-urjm_UY9BqXj=EaDTSA@mail.gmail.com
Discussion: http://postgr.es/m/CAFjFpRcitjfrULr5jfuKWRPsGUX0LQ0k8-yG0Qw2+1LBGNpMdw@mail.gmail.com
This commit is contained in:
Robert Haas
2017-10-06 11:11:10 -04:00
parent fe9ba28ee8
commit f49842d1ee
34 changed files with 4089 additions and 140 deletions

View File

@ -1075,3 +1075,29 @@ be desirable to postpone the Gather stage until as near to the top of the
plan as possible. Expanding the range of cases in which more work can be
pushed below the Gather (and costing them accurately) is likely to keep us
busy for a long time to come.
Partition-wise joins
--------------------
A join between two similarly partitioned tables can be broken down into joins
between their matching partitions if there exists an equi-join condition
between the partition keys of the joining tables. The equi-join between
partition keys implies that all join partners for a given row in one
partitioned table must be in the corresponding partition of the other
partitioned table. Because of this the join between partitioned tables to be
broken into joins between the matching partitions. The resultant join is
partitioned in the same way as the joining relations, thus allowing an N-way
join between similarly partitioned tables having equi-join condition between
their partition keys to be broken down into N-way joins between their matching
partitions. This technique of breaking down a join between partition tables
into join between their partitions is called partition-wise join. We will use
term "partitioned relation" for either a partitioned table or a join between
compatibly partitioned tables.
The partitioning properties of a partitioned relation are stored in its
RelOptInfo. The information about data types of partition keys are stored in
PartitionSchemeData structure. The planner maintains a list of canonical
partition schemes (distinct PartitionSchemeData objects) so that RelOptInfo of
any two partitioned relations with same partitioning scheme point to the same
PartitionSchemeData object. This reduces memory consumed by
PartitionSchemeData objects and makes it easy to compare the partition schemes
of joining relations.

View File

@ -264,6 +264,9 @@ merge_clump(PlannerInfo *root, List *clumps, Clump *new_clump, bool force)
/* Keep searching if join order is not valid */
if (joinrel)
{
/* Create paths for partition-wise joins. */
generate_partition_wise_join_paths(root, joinrel);
/* Create GatherPaths for any useful partial paths for rel */
generate_gather_paths(root, joinrel);

View File

@ -920,12 +920,79 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
childrel = find_base_rel(root, childRTindex);
Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
if (rel->part_scheme)
{
AttrNumber attno;
/*
* We need attr_needed data for building targetlist of a join
* relation representing join between matching partitions for
* partition-wise join. A given attribute of a child will be
* needed in the same highest joinrel where the corresponding
* attribute of parent is needed. Hence it suffices to use the
* same Relids set for parent and child.
*/
for (attno = rel->min_attr; attno <= rel->max_attr; attno++)
{
int index = attno - rel->min_attr;
Relids attr_needed = rel->attr_needed[index];
/* System attributes do not need translation. */
if (attno <= 0)
{
Assert(rel->min_attr == childrel->min_attr);
childrel->attr_needed[index] = attr_needed;
}
else
{
Var *var = list_nth_node(Var,
appinfo->translated_vars,
attno - 1);
int child_index;
child_index = var->varattno - childrel->min_attr;
childrel->attr_needed[child_index] = attr_needed;
}
}
}
/*
* We have to copy the parent's targetlist and quals to the child,
* with appropriate substitution of variables. However, only the
* baserestrictinfo quals are needed before we can check for
* constraint exclusion; so do that first and then check to see if we
* can disregard this child.
* Copy/Modify targetlist. Even if this child is deemed empty, we need
* its targetlist in case it falls on nullable side in a child-join
* because of partition-wise join.
*
* NB: the resulting childrel->reltarget->exprs may contain arbitrary
* expressions, which otherwise would not occur in a rel's targetlist.
* Code that might be looking at an appendrel child must cope with
* such. (Normally, a rel's targetlist would only include Vars and
* PlaceHolderVars.) XXX we do not bother to update the cost or width
* fields of childrel->reltarget; not clear if that would be useful.
*/
childrel->reltarget->exprs = (List *)
adjust_appendrel_attrs(root,
(Node *) rel->reltarget->exprs,
1, &appinfo);
/*
* We have to make child entries in the EquivalenceClass data
* structures as well. This is needed either if the parent
* participates in some eclass joins (because we will want to consider
* inner-indexscan joins on the individual children) or if the parent
* has useful pathkeys (because we should try to build MergeAppend
* paths that produce those sort orderings). Even if this child is
* deemed dummy, it may fall on nullable side in a child-join, which
* in turn may participate in a MergeAppend, where we will need the
* EquivalenceClass data structures.
*/
if (rel->has_eclass_joins || has_useful_pathkeys(root, rel))
add_child_rel_equivalences(root, appinfo, rel, childrel);
childrel->has_eclass_joins = rel->has_eclass_joins;
/*
* We have to copy the parent's quals to the child, with appropriate
* substitution of variables. However, only the baserestrictinfo
* quals are needed before we can check for constraint exclusion; so
* do that first and then check to see if we can disregard this child.
*
* The child rel's targetlist might contain non-Var expressions, which
* means that substitution into the quals could produce opportunities
@ -1052,44 +1119,11 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
continue;
}
/*
* CE failed, so finish copying/modifying targetlist and join quals.
*
* NB: the resulting childrel->reltarget->exprs may contain arbitrary
* expressions, which otherwise would not occur in a rel's targetlist.
* Code that might be looking at an appendrel child must cope with
* such. (Normally, a rel's targetlist would only include Vars and
* PlaceHolderVars.) XXX we do not bother to update the cost or width
* fields of childrel->reltarget; not clear if that would be useful.
*/
/* CE failed, so finish copying/modifying join quals. */
childrel->joininfo = (List *)
adjust_appendrel_attrs(root,
(Node *) rel->joininfo,
1, &appinfo);
childrel->reltarget->exprs = (List *)
adjust_appendrel_attrs(root,
(Node *) rel->reltarget->exprs,
1, &appinfo);
/*
* We have to make child entries in the EquivalenceClass data
* structures as well. This is needed either if the parent
* participates in some eclass joins (because we will want to consider
* inner-indexscan joins on the individual children) or if the parent
* has useful pathkeys (because we should try to build MergeAppend
* paths that produce those sort orderings).
*/
if (rel->has_eclass_joins || has_useful_pathkeys(root, rel))
add_child_rel_equivalences(root, appinfo, rel, childrel);
childrel->has_eclass_joins = rel->has_eclass_joins;
/*
* Note: we could compute appropriate attr_needed data for the child's
* variables, by transforming the parent's attr_needed through the
* translated_vars mapping. However, currently there's no need
* because attr_needed is only examined for base relations not
* otherrels. So we just leave the child's attr_needed empty.
*/
/*
* If parallelism is allowable for this query in general, see whether
@ -1262,14 +1296,14 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
live_childrels = lappend(live_childrels, childrel);
}
/* Add paths to the "append" relation. */
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
}
/*
* add_paths_to_append_rel
* Generate paths for given "append" relation given the set of non-dummy
* Generate paths for the given append relation given the set of non-dummy
* child rels.
*
* The function collects all parameterizations and orderings supported by the
@ -1293,30 +1327,44 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte;
bool build_partitioned_rels = false;
/*
* A root partition will already have a PartitionedChildRelInfo, and a
* non-root partitioned table doesn't need one, because its Append paths
* will get flattened into the parent anyway. For a subquery RTE, no
* PartitionedChildRelInfo exists; we collect all partitioned_rels
* associated with any child. (This assumes that we don't need to look
* through multiple levels of subquery RTEs; if we ever do, we could
* create a PartitionedChildRelInfo with the accumulated list of
* partitioned_rels which would then be found when populated our parent
* rel with paths. For the present, that appears to be unnecessary.)
*/
rte = planner_rt_fetch(rel->relid, root);
switch (rte->rtekind)
if (IS_SIMPLE_REL(rel))
{
case RTE_RELATION:
if (rte->relkind == RELKIND_PARTITIONED_TABLE)
partitioned_rels =
get_partitioned_child_rels(root, rel->relid);
break;
case RTE_SUBQUERY:
build_partitioned_rels = true;
break;
default:
elog(ERROR, "unexpected rtekind: %d", (int) rte->rtekind);
/*
* A root partition will already have a PartitionedChildRelInfo, and a
* non-root partitioned table doesn't need one, because its Append
* paths will get flattened into the parent anyway. For a subquery
* RTE, no PartitionedChildRelInfo exists; we collect all
* partitioned_rels associated with any child. (This assumes that we
* don't need to look through multiple levels of subquery RTEs; if we
* ever do, we could create a PartitionedChildRelInfo with the
* accumulated list of partitioned_rels which would then be found when
* populated our parent rel with paths. For the present, that appears
* to be unnecessary.)
*/
rte = planner_rt_fetch(rel->relid, root);
switch (rte->rtekind)
{
case RTE_RELATION:
if (rte->relkind == RELKIND_PARTITIONED_TABLE)
partitioned_rels =
get_partitioned_child_rels(root, rel->relid);
break;
case RTE_SUBQUERY:
build_partitioned_rels = true;
break;
default:
elog(ERROR, "unexpcted rtekind: %d", (int) rte->rtekind);
}
}
else if (rel->reloptkind == RELOPT_JOINREL && rel->part_scheme)
{
/*
* Associate PartitionedChildRelInfo of the root partitioned tables
* being joined with the root partitioned join (indicated by
* RELOPT_JOINREL).
*/
partitioned_rels = get_partitioned_child_rels_for_join(root,
rel->relids);
}
/*
@ -2422,16 +2470,22 @@ standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels)
join_search_one_level(root, lev);
/*
* Run generate_gather_paths() for each just-processed joinrel. We
* could not do this earlier because both regular and partial paths
* can get added to a particular joinrel at multiple times within
* join_search_one_level. After that, we're done creating paths for
* the joinrel, so run set_cheapest().
* Run generate_partition_wise_join_paths() and
* generate_gather_paths() for each just-processed joinrel. We could
* not do this earlier because both regular and partial paths can get
* added to a particular joinrel at multiple times within
* join_search_one_level.
*
* After that, we're done creating paths for the joinrel, so run
* set_cheapest().
*/
foreach(lc, root->join_rel_level[lev])
{
rel = (RelOptInfo *) lfirst(lc);
/* Create paths for partition-wise joins. */
generate_partition_wise_join_paths(root, rel);
/* Create GatherPaths for any useful partial paths for rel */
generate_gather_paths(root, rel);
@ -3179,6 +3233,82 @@ compute_parallel_worker(RelOptInfo *rel, double heap_pages, double index_pages)
return parallel_workers;
}
/*
* generate_partition_wise_join_paths
* Create paths representing partition-wise join for given partitioned
* join relation.
*
* This must not be called until after we are done adding paths for all
* child-joins. Otherwise, add_path might delete a path to which some path
* generated here has a reference.
*/
void
generate_partition_wise_join_paths(PlannerInfo *root, RelOptInfo *rel)
{
List *live_children = NIL;
int cnt_parts;
int num_parts;
RelOptInfo **part_rels;
/* Handle only join relations here. */
if (!IS_JOIN_REL(rel))
return;
/*
* If we've already proven this join is empty, we needn't consider any
* more paths for it.
*/
if (IS_DUMMY_REL(rel))
return;
/*
* Nothing to do if the relation is not partitioned. An outer join
* relation which had empty inner relation in every pair will have rest of
* the partitioning properties set except the child-join RelOptInfos. See
* try_partition_wise_join() for more explanation.
*/
if (rel->nparts <= 0 || rel->part_rels == NULL)
return;
/* Guard against stack overflow due to overly deep partition hierarchy. */
check_stack_depth();
num_parts = rel->nparts;
part_rels = rel->part_rels;
/* Collect non-dummy child-joins. */
for (cnt_parts = 0; cnt_parts < num_parts; cnt_parts++)
{
RelOptInfo *child_rel = part_rels[cnt_parts];
/* Add partition-wise join paths for partitioned child-joins. */
generate_partition_wise_join_paths(root, child_rel);
/* Dummy children will not be scanned, so ingore those. */
if (IS_DUMMY_REL(child_rel))
continue;
set_cheapest(child_rel);
#ifdef OPTIMIZER_DEBUG
debug_print_rel(root, rel);
#endif
live_children = lappend(live_children, child_rel);
}
/* If all child-joins are dummy, parent join is also dummy. */
if (!live_children)
{
mark_dummy_rel(rel);
return;
}
/* Build additional paths for this rel from child-join paths. */
add_paths_to_append_rel(root, rel, live_children);
list_free(live_children);
}
/*****************************************************************************
* DEBUG SUPPORT

View File

@ -127,6 +127,7 @@ bool enable_material = true;
bool enable_mergejoin = true;
bool enable_hashjoin = true;
bool enable_gathermerge = true;
bool enable_partition_wise_join = false;
typedef struct
{

View File

@ -26,9 +26,19 @@
/* Hook for plugins to get control in add_paths_to_joinrel() */
set_join_pathlist_hook_type set_join_pathlist_hook = NULL;
#define PATH_PARAM_BY_REL(path, rel) \
/*
* Paths parameterized by the parent can be considered to be parameterized by
* any of its child.
*/
#define PATH_PARAM_BY_PARENT(path, rel) \
((path)->param_info && bms_overlap(PATH_REQ_OUTER(path), \
(rel)->top_parent_relids))
#define PATH_PARAM_BY_REL_SELF(path, rel) \
((path)->param_info && bms_overlap(PATH_REQ_OUTER(path), (rel)->relids))
#define PATH_PARAM_BY_REL(path, rel) \
(PATH_PARAM_BY_REL_SELF(path, rel) || PATH_PARAM_BY_PARENT(path, rel))
static void try_partial_mergejoin_path(PlannerInfo *root,
RelOptInfo *joinrel,
Path *outer_path,
@ -115,6 +125,19 @@ add_paths_to_joinrel(PlannerInfo *root,
JoinPathExtraData extra;
bool mergejoin_allowed = true;
ListCell *lc;
Relids joinrelids;
/*
* PlannerInfo doesn't contain the SpecialJoinInfos created for joins
* between child relations, even if there is a SpecialJoinInfo node for
* the join between the topmost parents. So, while calculating Relids set
* representing the restriction, consider relids of topmost parent of
* partitions.
*/
if (joinrel->reloptkind == RELOPT_OTHER_JOINREL)
joinrelids = joinrel->top_parent_relids;
else
joinrelids = joinrel->relids;
extra.restrictlist = restrictlist;
extra.mergeclause_list = NIL;
@ -211,16 +234,16 @@ add_paths_to_joinrel(PlannerInfo *root,
* join has already been proven legal.) If the SJ is relevant, it
* presents constraints for joining to anything not in its RHS.
*/
if (bms_overlap(joinrel->relids, sjinfo2->min_righthand) &&
!bms_overlap(joinrel->relids, sjinfo2->min_lefthand))
if (bms_overlap(joinrelids, sjinfo2->min_righthand) &&
!bms_overlap(joinrelids, sjinfo2->min_lefthand))
extra.param_source_rels = bms_join(extra.param_source_rels,
bms_difference(root->all_baserels,
sjinfo2->min_righthand));
/* full joins constrain both sides symmetrically */
if (sjinfo2->jointype == JOIN_FULL &&
bms_overlap(joinrel->relids, sjinfo2->min_lefthand) &&
!bms_overlap(joinrel->relids, sjinfo2->min_righthand))
bms_overlap(joinrelids, sjinfo2->min_lefthand) &&
!bms_overlap(joinrelids, sjinfo2->min_righthand))
extra.param_source_rels = bms_join(extra.param_source_rels,
bms_difference(root->all_baserels,
sjinfo2->min_lefthand));
@ -347,11 +370,25 @@ try_nestloop_path(PlannerInfo *root,
JoinCostWorkspace workspace;
RelOptInfo *innerrel = inner_path->parent;
RelOptInfo *outerrel = outer_path->parent;
Relids innerrelids = innerrel->relids;
Relids outerrelids = outerrel->relids;
Relids innerrelids;
Relids outerrelids;
Relids inner_paramrels = PATH_REQ_OUTER(inner_path);
Relids outer_paramrels = PATH_REQ_OUTER(outer_path);
/*
* Paths are parameterized by top-level parents, so run parameterization
* tests on the parent relids.
*/
if (innerrel->top_parent_relids)
innerrelids = innerrel->top_parent_relids;
else
innerrelids = innerrel->relids;
if (outerrel->top_parent_relids)
outerrelids = outerrel->top_parent_relids;
else
outerrelids = outerrel->relids;
/*
* Check to see if proposed path is still parameterized, and reject if the
* parameterization wouldn't be sensible --- unless allow_star_schema_join
@ -387,6 +424,27 @@ try_nestloop_path(PlannerInfo *root,
workspace.startup_cost, workspace.total_cost,
pathkeys, required_outer))
{
/*
* If the inner path is parameterized, it is parameterized by the
* topmost parent of the outer rel, not the outer rel itself. Fix
* that.
*/
if (PATH_PARAM_BY_PARENT(inner_path, outer_path->parent))
{
inner_path = reparameterize_path_by_child(root, inner_path,
outer_path->parent);
/*
* If we could not translate the path, we can't create nest loop
* path.
*/
if (!inner_path)
{
bms_free(required_outer);
return;
}
}
add_path(joinrel, (Path *)
create_nestloop_path(root,
joinrel,
@ -432,8 +490,20 @@ try_partial_nestloop_path(PlannerInfo *root,
if (inner_path->param_info != NULL)
{
Relids inner_paramrels = inner_path->param_info->ppi_req_outer;
RelOptInfo *outerrel = outer_path->parent;
Relids outerrelids;
if (!bms_is_subset(inner_paramrels, outer_path->parent->relids))
/*
* The inner and outer paths are parameterized, if at all, by the top
* level parents, not the child relations, so we must use those relids
* for our paramaterization tests.
*/
if (outerrel->top_parent_relids)
outerrelids = outerrel->top_parent_relids;
else
outerrelids = outerrel->relids;
if (!bms_is_subset(inner_paramrels, outerrelids))
return;
}
@ -446,6 +516,22 @@ try_partial_nestloop_path(PlannerInfo *root,
if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys))
return;
/*
* If the inner path is parameterized, it is parameterized by the topmost
* parent of the outer rel, not the outer rel itself. Fix that.
*/
if (PATH_PARAM_BY_PARENT(inner_path, outer_path->parent))
{
inner_path = reparameterize_path_by_child(root, inner_path,
outer_path->parent);
/*
* If we could not translate the path, we can't create nest loop path.
*/
if (!inner_path)
return;
}
/* Might be good enough to be worth trying, so let's try it. */
add_partial_path(joinrel, (Path *)
create_nestloop_path(root,

View File

@ -14,10 +14,17 @@
*/
#include "postgres.h"
#include "miscadmin.h"
#include "catalog/partition.h"
#include "nodes/relation.h"
#include "optimizer/clauses.h"
#include "optimizer/joininfo.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/prep.h"
#include "optimizer/cost.h"
#include "utils/memutils.h"
#include "utils/lsyscache.h"
static void make_rels_by_clause_joins(PlannerInfo *root,
@ -29,12 +36,17 @@ static void make_rels_by_clauseless_joins(PlannerInfo *root,
static bool has_join_restriction(PlannerInfo *root, RelOptInfo *rel);
static bool has_legal_joinclause(PlannerInfo *root, RelOptInfo *rel);
static bool is_dummy_rel(RelOptInfo *rel);
static void mark_dummy_rel(RelOptInfo *rel);
static bool restriction_is_constant_false(List *restrictlist,
bool only_pushed_down);
static void populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
RelOptInfo *rel2, RelOptInfo *joinrel,
SpecialJoinInfo *sjinfo, List *restrictlist);
static void try_partition_wise_join(PlannerInfo *root, RelOptInfo *rel1,
RelOptInfo *rel2, RelOptInfo *joinrel,
SpecialJoinInfo *parent_sjinfo,
List *parent_restrictlist);
static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel,
bool strict_op);
/*
@ -892,6 +904,9 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
elog(ERROR, "unrecognized join type: %d", (int) sjinfo->jointype);
break;
}
/* Apply partition-wise join technique, if possible. */
try_partition_wise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
}
@ -1197,7 +1212,7 @@ is_dummy_rel(RelOptInfo *rel)
* is that the best solution is to explicitly make the dummy path in the same
* context the given RelOptInfo is in.
*/
static void
void
mark_dummy_rel(RelOptInfo *rel)
{
MemoryContext oldcontext;
@ -1268,3 +1283,300 @@ restriction_is_constant_false(List *restrictlist, bool only_pushed_down)
}
return false;
}
/*
* Assess whether join between given two partitioned relations can be broken
* down into joins between matching partitions; a technique called
* "partition-wise join"
*
* Partition-wise join is possible when a. Joining relations have same
* partitioning scheme b. There exists an equi-join between the partition keys
* of the two relations.
*
* Partition-wise join is planned as follows (details: optimizer/README.)
*
* 1. Create the RelOptInfos for joins between matching partitions i.e
* child-joins and add paths to them.
*
* 2. Construct Append or MergeAppend paths across the set of child joins.
* This second phase is implemented by generate_partition_wise_join_paths().
*
* The RelOptInfo, SpecialJoinInfo and restrictlist for each child join are
* obtained by translating the respective parent join structures.
*/
static void
try_partition_wise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
RelOptInfo *joinrel, SpecialJoinInfo *parent_sjinfo,
List *parent_restrictlist)
{
int nparts;
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))
return;
/*
* set_rel_pathlist() may not create paths in children of an empty
* partitioned table and so we can not add paths to child-joins. So, deem
* such a join as unpartitioned. When a partitioned relation is deemed
* empty because all its children are empty, dummy path will be set in
* each of the children. In such a case we could still consider the join
* as partitioned, but it might not help much.
*/
if (IS_DUMMY_REL(rel1) || IS_DUMMY_REL(rel2))
return;
/*
* 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.
*/
Assert(IS_PARTITIONED_REL(rel1) && IS_PARTITIONED_REL(rel2));
Assert(REL_HAS_ALL_PART_PROPS(rel1) && REL_HAS_ALL_PART_PROPS(rel2));
/*
* The partition scheme of the join relation should match that of the
* joining relations.
*/
Assert(joinrel->part_scheme == rel1->part_scheme &&
joinrel->part_scheme == rel2->part_scheme);
/*
* Since we allow partition-wise 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));
nparts = joinrel->nparts;
/* Allocate space to hold child-joins RelOptInfos, if not already done. */
if (!joinrel->part_rels)
joinrel->part_rels =
(RelOptInfo **) palloc0(sizeof(RelOptInfo *) * nparts);
/*
* 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++)
{
RelOptInfo *child_rel1 = rel1->part_rels[cnt_parts];
RelOptInfo *child_rel2 = rel2->part_rels[cnt_parts];
SpecialJoinInfo *child_sjinfo;
List *child_restrictlist;
RelOptInfo *child_joinrel;
Relids child_joinrelids;
AppendRelInfo **appinfos;
int nappinfos;
/* We should never try to join two overlapping sets of rels. */
Assert(!bms_overlap(child_rel1->relids, child_rel2->relids));
child_joinrelids = bms_union(child_rel1->relids, child_rel2->relids);
appinfos = find_appinfos_by_relids(root, child_joinrelids, &nappinfos);
/*
* Construct SpecialJoinInfo from parent join relations's
* SpecialJoinInfo.
*/
child_sjinfo = build_child_join_sjinfo(root, parent_sjinfo,
child_rel1->relids,
child_rel2->relids);
/*
* Construct restrictions applicable to the child join from those
* applicable to the parent join.
*/
child_restrictlist =
(List *) adjust_appendrel_attrs(root,
(Node *) parent_restrictlist,
nappinfos, appinfos);
pfree(appinfos);
child_joinrel = joinrel->part_rels[cnt_parts];
if (!child_joinrel)
{
child_joinrel = build_child_join_rel(root, child_rel1, child_rel2,
joinrel, child_restrictlist,
child_sjinfo,
child_sjinfo->jointype);
joinrel->part_rels[cnt_parts] = child_joinrel;
}
Assert(bms_equal(child_joinrel->relids, child_joinrelids));
populate_joinrel_with_paths(root, child_rel1, child_rel2,
child_joinrel, child_sjinfo,
child_restrictlist);
}
}
/*
* Returns true if there exists an equi-join condition for each pair of
* partition keys from given relations being joined.
*/
bool
have_partkey_equi_join(RelOptInfo *rel1, RelOptInfo *rel2, JoinType jointype,
List *restrictlist)
{
PartitionScheme part_scheme = rel1->part_scheme;
ListCell *lc;
int cnt_pks;
bool pk_has_clause[PARTITION_MAX_KEYS];
bool strict_op;
/*
* This function should be called when the joining relations have same
* partitioning scheme.
*/
Assert(rel1->part_scheme == rel2->part_scheme);
Assert(part_scheme);
memset(pk_has_clause, 0, sizeof(pk_has_clause));
foreach(lc, restrictlist)
{
RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
OpExpr *opexpr;
Expr *expr1;
Expr *expr2;
int ipk1;
int ipk2;
/* If processing an outer join, only use its own join clauses. */
if (IS_OUTER_JOIN(jointype) && rinfo->is_pushed_down)
continue;
/* Skip clauses which can not be used for a join. */
if (!rinfo->can_join)
continue;
/* Skip clauses which are not equality conditions. */
if (!rinfo->mergeopfamilies)
continue;
opexpr = (OpExpr *) rinfo->clause;
Assert(is_opclause(opexpr));
/*
* The equi-join between partition keys is strict if equi-join between
* at least one partition key is using a strict operator. See
* explanation about outer join reordering identity 3 in
* optimizer/README
*/
strict_op = op_strict(opexpr->opno);
/* Match the operands to the relation. */
if (bms_is_subset(rinfo->left_relids, rel1->relids) &&
bms_is_subset(rinfo->right_relids, rel2->relids))
{
expr1 = linitial(opexpr->args);
expr2 = lsecond(opexpr->args);
}
else if (bms_is_subset(rinfo->left_relids, rel2->relids) &&
bms_is_subset(rinfo->right_relids, rel1->relids))
{
expr1 = lsecond(opexpr->args);
expr2 = linitial(opexpr->args);
}
else
continue;
/*
* Only clauses referencing the partition keys are useful for
* partition-wise join.
*/
ipk1 = match_expr_to_partition_keys(expr1, rel1, strict_op);
if (ipk1 < 0)
continue;
ipk2 = match_expr_to_partition_keys(expr2, rel2, strict_op);
if (ipk2 < 0)
continue;
/*
* If the clause refers to keys at different ordinal positions, it can
* not be used for partition-wise join.
*/
if (ipk1 != ipk2)
continue;
/*
* The clause allows partition-wise join if only it uses the same
* operator family as that specified by the partition key.
*/
if (!list_member_oid(rinfo->mergeopfamilies,
part_scheme->partopfamily[ipk1]))
continue;
/* Mark the partition key as having an equi-join clause. */
pk_has_clause[ipk1] = true;
}
/* Check whether every partition key has an equi-join condition. */
for (cnt_pks = 0; cnt_pks < part_scheme->partnatts; cnt_pks++)
{
if (!pk_has_clause[cnt_pks])
return false;
}
return true;
}
/*
* Find the partition key from the given relation matching the given
* expression. If found, return the index of the partition key, else return -1.
*/
static int
match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op)
{
int cnt;
/* This function should be called only for partitioned relations. */
Assert(rel->part_scheme);
/* Remove any relabel decorations. */
while (IsA(expr, RelabelType))
expr = (Expr *) (castNode(RelabelType, expr))->arg;
for (cnt = 0; cnt < rel->part_scheme->partnatts; cnt++)
{
ListCell *lc;
Assert(rel->partexprs);
foreach(lc, rel->partexprs[cnt])
{
if (equal(lfirst(lc), expr))
return cnt;
}
if (!strict_op)
continue;
/*
* If it's a strict equi-join a NULL partition key on one side will
* not join a NULL partition key on the other side. So, rows with NULL
* partition key from a partition on one side can not join with those
* from a non-matching partition on the other side. So, search the
* nullable partition keys as well.
*/
Assert(rel->nullable_partexprs);
foreach(lc, rel->nullable_partexprs[cnt])
{
if (equal(lfirst(lc), expr))
return cnt;
}
}
return -1;
}

View File

@ -250,7 +250,8 @@ static Plan *prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
static EquivalenceMember *find_ec_member_for_tle(EquivalenceClass *ec,
TargetEntry *tle,
Relids relids);
static Sort *make_sort_from_pathkeys(Plan *lefttree, List *pathkeys);
static Sort *make_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
Relids relids);
static Sort *make_sort_from_groupcols(List *groupcls,
AttrNumber *grpColIdx,
Plan *lefttree);
@ -1652,7 +1653,7 @@ create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags)
subplan = create_plan_recurse(root, best_path->subpath,
flags | CP_SMALL_TLIST);
plan = make_sort_from_pathkeys(subplan, best_path->path.pathkeys);
plan = make_sort_from_pathkeys(subplan, best_path->path.pathkeys, NULL);
copy_generic_path_info(&plan->plan, (Path *) best_path);
@ -3771,6 +3772,8 @@ create_mergejoin_plan(PlannerInfo *root,
ListCell *lc;
ListCell *lop;
ListCell *lip;
Path *outer_path = best_path->jpath.outerjoinpath;
Path *inner_path = best_path->jpath.innerjoinpath;
/*
* MergeJoin can project, so we don't have to demand exact tlists from the
@ -3834,8 +3837,10 @@ create_mergejoin_plan(PlannerInfo *root,
*/
if (best_path->outersortkeys)
{
Relids outer_relids = outer_path->parent->relids;
Sort *sort = make_sort_from_pathkeys(outer_plan,
best_path->outersortkeys);
best_path->outersortkeys,
outer_relids);
label_sort_with_costsize(root, sort, -1.0);
outer_plan = (Plan *) sort;
@ -3846,8 +3851,10 @@ create_mergejoin_plan(PlannerInfo *root,
if (best_path->innersortkeys)
{
Relids inner_relids = inner_path->parent->relids;
Sort *sort = make_sort_from_pathkeys(inner_plan,
best_path->innersortkeys);
best_path->innersortkeys,
inner_relids);
label_sort_with_costsize(root, sort, -1.0);
inner_plan = (Plan *) sort;
@ -5525,8 +5532,9 @@ make_sort(Plan *lefttree, int numCols,
* the output parameters *p_numsortkeys etc.
*
* When looking for matches to an EquivalenceClass's members, we will only
* consider child EC members if they match 'relids'. This protects against
* possible incorrect matches to child expressions that contain no Vars.
* consider child EC members if they belong to given 'relids'. This protects
* against possible incorrect matches to child expressions that contain no
* Vars.
*
* If reqColIdx isn't NULL then it contains sort key column numbers that
* we should match. This is used when making child plans for a MergeAppend;
@ -5681,11 +5689,11 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
continue;
/*
* Ignore child members unless they match the rel being
* Ignore child members unless they belong to the rel being
* sorted.
*/
if (em->em_is_child &&
!bms_equal(em->em_relids, relids))
!bms_is_subset(em->em_relids, relids))
continue;
sortexpr = em->em_expr;
@ -5769,7 +5777,7 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
* find_ec_member_for_tle
* Locate an EquivalenceClass member matching the given TLE, if any
*
* Child EC members are ignored unless they match 'relids'.
* Child EC members are ignored unless they belong to given 'relids'.
*/
static EquivalenceMember *
find_ec_member_for_tle(EquivalenceClass *ec,
@ -5797,10 +5805,10 @@ find_ec_member_for_tle(EquivalenceClass *ec,
continue;
/*
* Ignore child members unless they match the rel being sorted.
* Ignore child members unless they belong to the rel being sorted.
*/
if (em->em_is_child &&
!bms_equal(em->em_relids, relids))
!bms_is_subset(em->em_relids, relids))
continue;
/* Match if same expression (after stripping relabel) */
@ -5821,9 +5829,10 @@ find_ec_member_for_tle(EquivalenceClass *ec,
*
* 'lefttree' is the node which yields input tuples
* 'pathkeys' is the list of pathkeys by which the result is to be sorted
* 'relids' is the set of relations required by prepare_sort_from_pathkeys()
*/
static Sort *
make_sort_from_pathkeys(Plan *lefttree, List *pathkeys)
make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids)
{
int numsortkeys;
AttrNumber *sortColIdx;
@ -5833,7 +5842,7 @@ make_sort_from_pathkeys(Plan *lefttree, List *pathkeys)
/* Compute sort column info, and adjust lefttree as needed */
lefttree = prepare_sort_from_pathkeys(lefttree, pathkeys,
NULL,
relids,
NULL,
false,
&numsortkeys,

View File

@ -6150,3 +6150,25 @@ get_partitioned_child_rels(PlannerInfo *root, Index rti)
return result;
}
/*
* get_partitioned_child_rels_for_join
* Build and return a list containing the RTI of every partitioned
* relation which is a child of some rel included in the join.
*/
List *
get_partitioned_child_rels_for_join(PlannerInfo *root, Relids join_relids)
{
List *result = NIL;
ListCell *l;
foreach(l, root->pcinfo_list)
{
PartitionedChildRelInfo *pc = lfirst(l);
if (bms_is_member(pc->parent_relid, join_relids))
result = list_concat(result, list_copy(pc->child_rels));
}
return result;
}

View File

@ -41,6 +41,9 @@ typedef struct
int num_vars; /* number of plain Var tlist entries */
bool has_ph_vars; /* are there PlaceHolderVar entries? */
bool has_non_vars; /* are there other entries? */
bool has_conv_whole_rows; /* are there ConvertRowtypeExpr
* entries encapsulating a whole-row
* Var? */
tlist_vinfo vars[FLEXIBLE_ARRAY_MEMBER]; /* has num_vars entries */
} indexed_tlist;
@ -139,6 +142,7 @@ static List *set_returning_clause_references(PlannerInfo *root,
int rtoffset);
static bool extract_query_dependencies_walker(Node *node,
PlannerInfo *context);
static bool is_converted_whole_row_reference(Node *node);
/*****************************************************************************
*
@ -1944,6 +1948,7 @@ build_tlist_index(List *tlist)
itlist->tlist = tlist;
itlist->has_ph_vars = false;
itlist->has_non_vars = false;
itlist->has_conv_whole_rows = false;
/* Find the Vars and fill in the index array */
vinfo = itlist->vars;
@ -1962,6 +1967,8 @@ build_tlist_index(List *tlist)
}
else if (tle->expr && IsA(tle->expr, PlaceHolderVar))
itlist->has_ph_vars = true;
else if (is_converted_whole_row_reference((Node *) tle->expr))
itlist->has_conv_whole_rows = true;
else
itlist->has_non_vars = true;
}
@ -1977,7 +1984,10 @@ build_tlist_index(List *tlist)
* This is like build_tlist_index, but we only index tlist entries that
* are Vars belonging to some rel other than the one specified. We will set
* has_ph_vars (allowing PlaceHolderVars to be matched), but not has_non_vars
* (so nothing other than Vars and PlaceHolderVars can be matched).
* (so nothing other than Vars and PlaceHolderVars can be matched). In case of
* DML, where this function will be used, returning lists from child relations
* will be appended similar to a simple append relation. That does not require
* fixing ConvertRowtypeExpr references. So, those are not considered here.
*/
static indexed_tlist *
build_tlist_index_other_vars(List *tlist, Index ignore_rel)
@ -1994,6 +2004,7 @@ build_tlist_index_other_vars(List *tlist, Index ignore_rel)
itlist->tlist = tlist;
itlist->has_ph_vars = false;
itlist->has_non_vars = false;
itlist->has_conv_whole_rows = false;
/* Find the desired Vars and fill in the index array */
vinfo = itlist->vars;
@ -2197,6 +2208,7 @@ static Node *
fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
{
Var *newvar;
bool converted_whole_row;
if (node == NULL)
return NULL;
@ -2266,8 +2278,12 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
}
if (IsA(node, Param))
return fix_param_node(context->root, (Param *) node);
/* Try matching more complex expressions too, if tlists have any */
if (context->outer_itlist && context->outer_itlist->has_non_vars)
converted_whole_row = is_converted_whole_row_reference(node);
if (context->outer_itlist &&
(context->outer_itlist->has_non_vars ||
(context->outer_itlist->has_conv_whole_rows && converted_whole_row)))
{
newvar = search_indexed_tlist_for_non_var((Expr *) node,
context->outer_itlist,
@ -2275,7 +2291,9 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
if (newvar)
return (Node *) newvar;
}
if (context->inner_itlist && context->inner_itlist->has_non_vars)
if (context->inner_itlist &&
(context->inner_itlist->has_non_vars ||
(context->inner_itlist->has_conv_whole_rows && converted_whole_row)))
{
newvar = search_indexed_tlist_for_non_var((Expr *) node,
context->inner_itlist,
@ -2395,7 +2413,9 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context)
/* If no match, just fall through to process it normally */
}
/* Try matching more complex expressions too, if tlist has any */
if (context->subplan_itlist->has_non_vars)
if (context->subplan_itlist->has_non_vars ||
(context->subplan_itlist->has_conv_whole_rows &&
is_converted_whole_row_reference(node)))
{
newvar = search_indexed_tlist_for_non_var((Expr *) node,
context->subplan_itlist,
@ -2602,3 +2622,33 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
return expression_tree_walker(node, extract_query_dependencies_walker,
(void *) context);
}
/*
* is_converted_whole_row_reference
* If the given node is a ConvertRowtypeExpr encapsulating a whole-row
* reference as implicit cast, return true. Otherwise return false.
*/
static bool
is_converted_whole_row_reference(Node *node)
{
ConvertRowtypeExpr *convexpr;
if (!node || !IsA(node, ConvertRowtypeExpr))
return false;
/* Traverse nested ConvertRowtypeExpr's. */
convexpr = castNode(ConvertRowtypeExpr, node);
while (convexpr->convertformat == COERCE_IMPLICIT_CAST &&
IsA(convexpr->arg, ConvertRowtypeExpr))
convexpr = castNode(ConvertRowtypeExpr, convexpr->arg);
if (IsA(convexpr->arg, Var))
{
Var *var = castNode(Var, convexpr->arg);
if (var->varattno == 0)
return true;
}
return false;
}

View File

@ -2269,6 +2269,59 @@ adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos)
return relids;
}
/*
* Replace any relid present in top_parent_relids with its child in
* child_relids. Members of child_relids can be multiple levels below top
* parent in the partition hierarchy.
*/
Relids
adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
Relids child_relids, Relids top_parent_relids)
{
AppendRelInfo **appinfos;
int nappinfos;
Relids parent_relids = NULL;
Relids result;
Relids tmp_result = NULL;
int cnt;
/*
* If the given relids set doesn't contain any of the top parent relids,
* it will remain unchanged.
*/
if (!bms_overlap(relids, top_parent_relids))
return relids;
appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos);
/* Construct relids set for the immediate parent of the given child. */
for (cnt = 0; cnt < nappinfos; cnt++)
{
AppendRelInfo *appinfo = appinfos[cnt];
parent_relids = bms_add_member(parent_relids, appinfo->parent_relid);
}
/* Recurse if immediate parent is not the top parent. */
if (!bms_equal(parent_relids, top_parent_relids))
{
tmp_result = adjust_child_relids_multilevel(root, relids,
parent_relids,
top_parent_relids);
relids = tmp_result;
}
result = adjust_child_relids(relids, nappinfos, appinfos);
/* Free memory consumed by any intermediate result. */
if (tmp_result)
bms_free(tmp_result);
bms_free(parent_relids);
pfree(appinfos);
return result;
}
/*
* Adjust the targetlist entries of an inherited UPDATE operation
*
@ -2408,6 +2461,48 @@ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
return node;
}
/*
* Construct the SpecialJoinInfo for a child-join by translating
* SpecialJoinInfo for the join between parents. left_relids and right_relids
* are the relids of left and right side of the join respectively.
*/
SpecialJoinInfo *
build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo,
Relids left_relids, Relids right_relids)
{
SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo);
AppendRelInfo **left_appinfos;
int left_nappinfos;
AppendRelInfo **right_appinfos;
int right_nappinfos;
memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo));
left_appinfos = find_appinfos_by_relids(root, left_relids,
&left_nappinfos);
right_appinfos = find_appinfos_by_relids(root, right_relids,
&right_nappinfos);
sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand,
left_nappinfos, left_appinfos);
sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand,
right_nappinfos,
right_appinfos);
sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand,
left_nappinfos, left_appinfos);
sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand,
right_nappinfos,
right_appinfos);
sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root,
(Node *) sjinfo->semi_rhs_exprs,
right_nappinfos,
right_appinfos);
pfree(left_appinfos);
pfree(right_appinfos);
return sjinfo;
}
/*
* find_appinfos_by_relids
* Find AppendRelInfo structures for all relations specified by relids.

View File

@ -18,15 +18,20 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "nodes/extensible.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/planmain.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "parser/parsetree.h"
#include "foreign/fdwapi.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/selfuncs.h"
@ -46,6 +51,9 @@ typedef enum
#define STD_FUZZ_FACTOR 1.01
static List *translate_sub_tlist(List *tlist, int relid);
static List *reparameterize_pathlist_by_child(PlannerInfo *root,
List *pathlist,
RelOptInfo *child_rel);
/*****************************************************************************
@ -3429,3 +3437,358 @@ reparameterize_path(PlannerInfo *root, Path *path,
}
return NULL;
}
/*
* reparameterize_path_by_child
* Given a path parameterized by the parent of the given child relation,
* translate the path to be parameterized by the given child relation.
*
* The function creates a new path of the same type as the given path, but
* parameterized by the given child relation. Most fields from the original
* path can simply be flat-copied, but any expressions must be adjusted to
* refer to the correct varnos, and any paths must be recursively
* reparameterized. Other fields that refer to specific relids also need
* adjustment.
*
* The cost, number of rows, width and parallel path properties depend upon
* path->parent, which does not change during the translation. Hence those
* members are copied as they are.
*
* If the given path can not be reparameterized, the function returns NULL.
*/
Path *
reparameterize_path_by_child(PlannerInfo *root, Path *path,
RelOptInfo *child_rel)
{
#define FLAT_COPY_PATH(newnode, node, nodetype) \
( (newnode) = makeNode(nodetype), \
memcpy((newnode), (node), sizeof(nodetype)) )
#define ADJUST_CHILD_ATTRS(node) \
((node) = \
(List *) adjust_appendrel_attrs_multilevel(root, (Node *) (node), \
child_rel->relids, \
child_rel->top_parent_relids))
#define REPARAMETERIZE_CHILD_PATH(path) \
do { \
(path) = reparameterize_path_by_child(root, (path), child_rel); \
if ((path) == NULL) \
return NULL; \
} while(0);
#define REPARAMETERIZE_CHILD_PATH_LIST(pathlist) \
do { \
if ((pathlist) != NIL) \
{ \
(pathlist) = reparameterize_pathlist_by_child(root, (pathlist), \
child_rel); \
if ((pathlist) == NIL) \
return NULL; \
} \
} while(0);
Path *new_path;
ParamPathInfo *new_ppi;
ParamPathInfo *old_ppi;
Relids required_outer;
/*
* If the path is not parameterized by parent of the given relation, it
* doesn't need reparameterization.
*/
if (!path->param_info ||
!bms_overlap(PATH_REQ_OUTER(path), child_rel->top_parent_relids))
return path;
/* Reparameterize a copy of given path. */
switch (nodeTag(path))
{
case T_Path:
FLAT_COPY_PATH(new_path, path, Path);
break;
case T_IndexPath:
{
IndexPath *ipath;
FLAT_COPY_PATH(ipath, path, IndexPath);
ADJUST_CHILD_ATTRS(ipath->indexclauses);
ADJUST_CHILD_ATTRS(ipath->indexquals);
new_path = (Path *) ipath;
}
break;
case T_BitmapHeapPath:
{
BitmapHeapPath *bhpath;
FLAT_COPY_PATH(bhpath, path, BitmapHeapPath);
REPARAMETERIZE_CHILD_PATH(bhpath->bitmapqual);
new_path = (Path *) bhpath;
}
break;
case T_BitmapAndPath:
{
BitmapAndPath *bapath;
FLAT_COPY_PATH(bapath, path, BitmapAndPath);
REPARAMETERIZE_CHILD_PATH_LIST(bapath->bitmapquals);
new_path = (Path *) bapath;
}
break;
case T_BitmapOrPath:
{
BitmapOrPath *bopath;
FLAT_COPY_PATH(bopath, path, BitmapOrPath);
REPARAMETERIZE_CHILD_PATH_LIST(bopath->bitmapquals);
new_path = (Path *) bopath;
}
break;
case T_TidPath:
{
TidPath *tpath;
/*
* TidPath contains tidquals, which do not contain any
* external parameters per create_tidscan_path(). So don't
* bother to translate those.
*/
FLAT_COPY_PATH(tpath, path, TidPath);
new_path = (Path *) tpath;
}
break;
case T_ForeignPath:
{
ForeignPath *fpath;
ReparameterizeForeignPathByChild_function rfpc_func;
FLAT_COPY_PATH(fpath, path, ForeignPath);
if (fpath->fdw_outerpath)
REPARAMETERIZE_CHILD_PATH(fpath->fdw_outerpath);
/* Hand over to FDW if needed. */
rfpc_func =
path->parent->fdwroutine->ReparameterizeForeignPathByChild;
if (rfpc_func)
fpath->fdw_private = rfpc_func(root, fpath->fdw_private,
child_rel);
new_path = (Path *) fpath;
}
break;
case T_CustomPath:
{
CustomPath *cpath;
FLAT_COPY_PATH(cpath, path, CustomPath);
REPARAMETERIZE_CHILD_PATH_LIST(cpath->custom_paths);
if (cpath->methods &&
cpath->methods->ReparameterizeCustomPathByChild)
cpath->custom_private =
cpath->methods->ReparameterizeCustomPathByChild(root,
cpath->custom_private,
child_rel);
new_path = (Path *) cpath;
}
break;
case T_NestPath:
{
JoinPath *jpath;
FLAT_COPY_PATH(jpath, path, NestPath);
REPARAMETERIZE_CHILD_PATH(jpath->outerjoinpath);
REPARAMETERIZE_CHILD_PATH(jpath->innerjoinpath);
ADJUST_CHILD_ATTRS(jpath->joinrestrictinfo);
new_path = (Path *) jpath;
}
break;
case T_MergePath:
{
JoinPath *jpath;
MergePath *mpath;
FLAT_COPY_PATH(mpath, path, MergePath);
jpath = (JoinPath *) mpath;
REPARAMETERIZE_CHILD_PATH(jpath->outerjoinpath);
REPARAMETERIZE_CHILD_PATH(jpath->innerjoinpath);
ADJUST_CHILD_ATTRS(jpath->joinrestrictinfo);
ADJUST_CHILD_ATTRS(mpath->path_mergeclauses);
new_path = (Path *) mpath;
}
break;
case T_HashPath:
{
JoinPath *jpath;
HashPath *hpath;
FLAT_COPY_PATH(hpath, path, HashPath);
jpath = (JoinPath *) hpath;
REPARAMETERIZE_CHILD_PATH(jpath->outerjoinpath);
REPARAMETERIZE_CHILD_PATH(jpath->innerjoinpath);
ADJUST_CHILD_ATTRS(jpath->joinrestrictinfo);
ADJUST_CHILD_ATTRS(hpath->path_hashclauses);
new_path = (Path *) hpath;
}
break;
case T_AppendPath:
{
AppendPath *apath;
FLAT_COPY_PATH(apath, path, AppendPath);
REPARAMETERIZE_CHILD_PATH_LIST(apath->subpaths);
new_path = (Path *) apath;
}
break;
case T_MergeAppend:
{
MergeAppendPath *mapath;
FLAT_COPY_PATH(mapath, path, MergeAppendPath);
REPARAMETERIZE_CHILD_PATH_LIST(mapath->subpaths);
new_path = (Path *) mapath;
}
break;
case T_MaterialPath:
{
MaterialPath *mpath;
FLAT_COPY_PATH(mpath, path, MaterialPath);
REPARAMETERIZE_CHILD_PATH(mpath->subpath);
new_path = (Path *) mpath;
}
break;
case T_UniquePath:
{
UniquePath *upath;
FLAT_COPY_PATH(upath, path, UniquePath);
REPARAMETERIZE_CHILD_PATH(upath->subpath);
ADJUST_CHILD_ATTRS(upath->uniq_exprs);
new_path = (Path *) upath;
}
break;
case T_GatherPath:
{
GatherPath *gpath;
FLAT_COPY_PATH(gpath, path, GatherPath);
REPARAMETERIZE_CHILD_PATH(gpath->subpath);
new_path = (Path *) gpath;
}
break;
case T_GatherMergePath:
{
GatherMergePath *gmpath;
FLAT_COPY_PATH(gmpath, path, GatherMergePath);
REPARAMETERIZE_CHILD_PATH(gmpath->subpath);
new_path = (Path *) gmpath;
}
break;
default:
/* We don't know how to reparameterize this path. */
return NULL;
}
/*
* Adjust the parameterization information, which refers to the topmost
* parent. The topmost parent can be multiple levels away from the given
* child, hence use multi-level expression adjustment routines.
*/
old_ppi = new_path->param_info;
required_outer =
adjust_child_relids_multilevel(root, old_ppi->ppi_req_outer,
child_rel->relids,
child_rel->top_parent_relids);
/* If we already have a PPI for this parameterization, just return it */
new_ppi = find_param_path_info(new_path->parent, required_outer);
/*
* If not, build a new one and link it to the list of PPIs. For the same
* reason as explained in mark_dummy_rel(), allocate new PPI in the same
* context the given RelOptInfo is in.
*/
if (new_ppi == NULL)
{
MemoryContext oldcontext;
RelOptInfo *rel = path->parent;
oldcontext = MemoryContextSwitchTo(GetMemoryChunkContext(rel));
new_ppi = makeNode(ParamPathInfo);
new_ppi->ppi_req_outer = bms_copy(required_outer);
new_ppi->ppi_rows = old_ppi->ppi_rows;
new_ppi->ppi_clauses = old_ppi->ppi_clauses;
ADJUST_CHILD_ATTRS(new_ppi->ppi_clauses);
rel->ppilist = lappend(rel->ppilist, new_ppi);
MemoryContextSwitchTo(oldcontext);
}
bms_free(required_outer);
new_path->param_info = new_ppi;
/*
* Adjust the path target if the parent of the outer relation is
* referenced in the targetlist. This can happen when only the parent of
* outer relation is laterally referenced in this relation.
*/
if (bms_overlap(path->parent->lateral_relids,
child_rel->top_parent_relids))
{
new_path->pathtarget = copy_pathtarget(new_path->pathtarget);
ADJUST_CHILD_ATTRS(new_path->pathtarget->exprs);
}
return new_path;
}
/*
* reparameterize_pathlist_by_child
* Helper function to reparameterize a list of paths by given child rel.
*/
static List *
reparameterize_pathlist_by_child(PlannerInfo *root,
List *pathlist,
RelOptInfo *child_rel)
{
ListCell *lc;
List *result = NIL;
foreach(lc, pathlist)
{
Path *path = reparameterize_path_by_child(root, lfirst(lc),
child_rel);
if (path == NULL)
{
list_free(result);
return NIL;
}
result = lappend(result, path);
}
return result;
}

View File

@ -20,6 +20,7 @@
#include "optimizer/pathnode.h"
#include "optimizer/placeholder.h"
#include "optimizer/planmain.h"
#include "optimizer/prep.h"
#include "optimizer/var.h"
#include "utils/lsyscache.h"
@ -414,6 +415,10 @@ add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
Relids relids = joinrel->relids;
ListCell *lc;
/* This function is called only on the parent relations. */
Assert(!IS_OTHER_REL(joinrel) && !IS_OTHER_REL(outer_rel) &&
!IS_OTHER_REL(inner_rel));
foreach(lc, root->placeholder_list)
{
PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
@ -459,3 +464,56 @@ add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
}
}
}
/*
* add_placeholders_to_child_joinrel
* Translate the PHVs in parent's targetlist and add them to the child's
* targetlist. Also adjust the cost
*/
void
add_placeholders_to_child_joinrel(PlannerInfo *root, RelOptInfo *childrel,
RelOptInfo *parentrel)
{
ListCell *lc;
AppendRelInfo **appinfos;
int nappinfos;
Assert(IS_JOIN_REL(childrel) && IS_JOIN_REL(parentrel));
Assert(IS_OTHER_REL(childrel));
/* Nothing to do if no PHVs. */
if (root->placeholder_list == NIL)
return;
appinfos = find_appinfos_by_relids(root, childrel->relids, &nappinfos);
foreach(lc, parentrel->reltarget->exprs)
{
PlaceHolderVar *phv = lfirst(lc);
if (IsA(phv, PlaceHolderVar))
{
/*
* In case the placeholder Var refers to any of the parent
* relations, translate it to refer to the corresponding child.
*/
if (bms_overlap(phv->phrels, parentrel->relids) &&
childrel->reloptkind == RELOPT_OTHER_JOINREL)
{
phv = (PlaceHolderVar *) adjust_appendrel_attrs(root,
(Node *) phv,
nappinfos,
appinfos);
}
childrel->reltarget->exprs = lappend(childrel->reltarget->exprs,
phv);
}
}
/* Adjust the cost and width of child targetlist. */
childrel->reltarget->cost.startup = parentrel->reltarget->cost.startup;
childrel->reltarget->cost.per_tuple = parentrel->reltarget->cost.per_tuple;
childrel->reltarget->width = parentrel->reltarget->width;
pfree(appinfos);
}

View File

@ -71,7 +71,8 @@ static List *get_relation_statistics(RelOptInfo *rel, Relation relation);
static void set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel,
Relation relation);
static PartitionScheme find_partition_scheme(PlannerInfo *root, Relation rel);
static List **build_baserel_partition_key_exprs(Relation relation, Index varno);
static void set_baserel_partition_key_exprs(Relation relation,
RelOptInfo *rel);
/*
* get_relation_info -
@ -1832,7 +1833,7 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel,
Assert(partdesc != NULL && rel->part_scheme != NULL);
rel->boundinfo = partdesc->boundinfo;
rel->nparts = partdesc->nparts;
rel->partexprs = build_baserel_partition_key_exprs(relation, rel->relid);
set_baserel_partition_key_exprs(relation, rel);
}
/*
@ -1907,21 +1908,24 @@ find_partition_scheme(PlannerInfo *root, Relation relation)
}
/*
* build_baserel_partition_key_exprs
* set_baserel_partition_key_exprs
*
* Collects partition key expressions for a given base relation. Any single
* column partition keys are converted to Var nodes. All Var nodes are set
* to the given varno. The partition key expressions are returned as an array
* of single element lists to be stored in RelOptInfo of the base relation.
* Builds partition key expressions for the given base relation and sets them
* in given RelOptInfo. Any single column partition keys are converted to Var
* nodes. All Var nodes are restamped with the relid of given relation.
*/
static List **
build_baserel_partition_key_exprs(Relation relation, Index varno)
static void
set_baserel_partition_key_exprs(Relation relation,
RelOptInfo *rel)
{
PartitionKey partkey = RelationGetPartitionKey(relation);
int partnatts;
int cnt;
List **partexprs;
ListCell *lc;
Index varno = rel->relid;
Assert(IS_SIMPLE_REL(rel) && rel->relid > 0);
/* A partitioned table should have a partition key. */
Assert(partkey != NULL);
@ -1959,5 +1963,13 @@ build_baserel_partition_key_exprs(Relation relation, Index varno)
partexprs[cnt] = list_make1(partexpr);
}
return partexprs;
rel->partexprs = partexprs;
/*
* A base relation can not have nullable partition key expressions. We
* still allocate array of empty expressions lists to keep partition key
* expression handling code simple. See build_joinrel_partition_info() and
* match_expr_to_partition_keys().
*/
rel->nullable_partexprs = (List **) palloc0(sizeof(List *) * partnatts);
}

View File

@ -17,12 +17,14 @@
#include <limits.h>
#include "miscadmin.h"
#include "catalog/partition.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/placeholder.h"
#include "optimizer/plancat.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/tlist.h"
#include "utils/hsearch.h"
@ -52,6 +54,9 @@ static List *subbuild_joinrel_joinlist(RelOptInfo *joinrel,
static void set_foreign_rel_properties(RelOptInfo *joinrel,
RelOptInfo *outer_rel, RelOptInfo *inner_rel);
static void add_join_rel(PlannerInfo *root, RelOptInfo *joinrel);
static void build_joinrel_partition_info(RelOptInfo *joinrel,
RelOptInfo *outer_rel, RelOptInfo *inner_rel,
List *restrictlist, JoinType jointype);
/*
@ -151,6 +156,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
rel->boundinfo = NULL;
rel->part_rels = NULL;
rel->partexprs = NULL;
rel->nullable_partexprs = NULL;
/*
* Pass top parent's relids down the inheritance hierarchy. If the parent
@ -481,6 +487,9 @@ build_join_rel(PlannerInfo *root,
RelOptInfo *joinrel;
List *restrictlist;
/* This function should be used only for join between parents. */
Assert(!IS_OTHER_REL(outer_rel) && !IS_OTHER_REL(inner_rel));
/*
* See if we already have a joinrel for this set of base rels.
*/
@ -560,6 +569,7 @@ build_join_rel(PlannerInfo *root,
joinrel->boundinfo = NULL;
joinrel->part_rels = NULL;
joinrel->partexprs = NULL;
joinrel->nullable_partexprs = NULL;
/* Compute information relevant to the foreign relations. */
set_foreign_rel_properties(joinrel, outer_rel, inner_rel);
@ -605,6 +615,10 @@ build_join_rel(PlannerInfo *root,
*/
joinrel->has_eclass_joins = has_relevant_eclass_joinclause(root, joinrel);
/* Store the partition information. */
build_joinrel_partition_info(joinrel, outer_rel, inner_rel, restrictlist,
sjinfo->jointype);
/*
* Set estimates of the joinrel's size.
*/
@ -650,6 +664,138 @@ build_join_rel(PlannerInfo *root,
return joinrel;
}
/*
* build_child_join_rel
* Builds RelOptInfo representing join between given two child relations.
*
* 'outer_rel' and 'inner_rel' are the RelOptInfos of child relations being
* joined
* 'parent_joinrel' is the RelOptInfo representing the join between parent
* relations. Some of the members of new RelOptInfo are produced by
* translating corresponding members of this RelOptInfo
* 'sjinfo': child-join context info
* 'restrictlist': list of RestrictInfo nodes that apply to this particular
* pair of joinable relations
* 'join_appinfos': list of AppendRelInfo nodes for base child relations
* involved in this join
*/
RelOptInfo *
build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
RelOptInfo *inner_rel, RelOptInfo *parent_joinrel,
List *restrictlist, SpecialJoinInfo *sjinfo,
JoinType jointype)
{
RelOptInfo *joinrel = makeNode(RelOptInfo);
AppendRelInfo **appinfos;
int nappinfos;
/* Only joins between "other" relations land here. */
Assert(IS_OTHER_REL(outer_rel) && IS_OTHER_REL(inner_rel));
joinrel->reloptkind = RELOPT_OTHER_JOINREL;
joinrel->relids = bms_union(outer_rel->relids, inner_rel->relids);
joinrel->rows = 0;
/* cheap startup cost is interesting iff not all tuples to be retrieved */
joinrel->consider_startup = (root->tuple_fraction > 0);
joinrel->consider_param_startup = false;
joinrel->consider_parallel = false;
joinrel->reltarget = create_empty_pathtarget();
joinrel->pathlist = NIL;
joinrel->ppilist = NIL;
joinrel->partial_pathlist = NIL;
joinrel->cheapest_startup_path = NULL;
joinrel->cheapest_total_path = NULL;
joinrel->cheapest_unique_path = NULL;
joinrel->cheapest_parameterized_paths = NIL;
joinrel->direct_lateral_relids = NULL;
joinrel->lateral_relids = NULL;
joinrel->relid = 0; /* indicates not a baserel */
joinrel->rtekind = RTE_JOIN;
joinrel->min_attr = 0;
joinrel->max_attr = 0;
joinrel->attr_needed = NULL;
joinrel->attr_widths = NULL;
joinrel->lateral_vars = NIL;
joinrel->lateral_referencers = NULL;
joinrel->indexlist = NIL;
joinrel->pages = 0;
joinrel->tuples = 0;
joinrel->allvisfrac = 0;
joinrel->subroot = NULL;
joinrel->subplan_params = NIL;
joinrel->serverid = InvalidOid;
joinrel->userid = InvalidOid;
joinrel->useridiscurrent = false;
joinrel->fdwroutine = NULL;
joinrel->fdw_private = NULL;
joinrel->baserestrictinfo = NIL;
joinrel->baserestrictcost.startup = 0;
joinrel->baserestrictcost.per_tuple = 0;
joinrel->joininfo = NIL;
joinrel->has_eclass_joins = false;
joinrel->top_parent_relids = NULL;
joinrel->part_scheme = NULL;
joinrel->part_rels = NULL;
joinrel->partexprs = NULL;
joinrel->nullable_partexprs = NULL;
joinrel->top_parent_relids = bms_union(outer_rel->top_parent_relids,
inner_rel->top_parent_relids);
/* Compute information relevant to foreign relations. */
set_foreign_rel_properties(joinrel, outer_rel, inner_rel);
/* Build targetlist */
build_joinrel_tlist(root, joinrel, outer_rel);
build_joinrel_tlist(root, joinrel, inner_rel);
/* Add placeholder variables. */
add_placeholders_to_child_joinrel(root, joinrel, parent_joinrel);
/* Construct joininfo list. */
appinfos = find_appinfos_by_relids(root, joinrel->relids, &nappinfos);
joinrel->joininfo = (List *) adjust_appendrel_attrs(root,
(Node *) parent_joinrel->joininfo,
nappinfos,
appinfos);
pfree(appinfos);
/*
* Lateral relids referred in child join will be same as that referred in
* the parent relation. Throw any partial result computed while building
* the targetlist.
*/
bms_free(joinrel->direct_lateral_relids);
bms_free(joinrel->lateral_relids);
joinrel->direct_lateral_relids = (Relids) bms_copy(parent_joinrel->direct_lateral_relids);
joinrel->lateral_relids = (Relids) bms_copy(parent_joinrel->lateral_relids);
/*
* If the parent joinrel has pending equivalence classes, so does the
* child.
*/
joinrel->has_eclass_joins = parent_joinrel->has_eclass_joins;
/* Is the join between partitions itself partitioned? */
build_joinrel_partition_info(joinrel, outer_rel, inner_rel, restrictlist,
jointype);
/* Child joinrel is parallel safe if parent is parallel safe. */
joinrel->consider_parallel = parent_joinrel->consider_parallel;
/* Set estimates of the child-joinrel's size. */
set_joinrel_size_estimates(root, joinrel, outer_rel, inner_rel,
sjinfo, restrictlist);
/* We build the join only once. */
Assert(!find_join_rel(root, joinrel->relids));
/* Add the relation to the PlannerInfo. */
add_join_rel(root, joinrel);
return joinrel;
}
/*
* min_join_parameterization
*
@ -705,9 +851,15 @@ static void
build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *input_rel)
{
Relids relids = joinrel->relids;
Relids relids;
ListCell *vars;
/* attrs_needed refers to parent relids and not those of a child. */
if (joinrel->top_parent_relids)
relids = joinrel->top_parent_relids;
else
relids = joinrel->relids;
foreach(vars, input_rel->reltarget->exprs)
{
Var *var = (Var *) lfirst(vars);
@ -722,24 +874,55 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
continue;
/*
* Otherwise, anything in a baserel or joinrel targetlist ought to be
* a Var. (More general cases can only appear in appendrel child
* rels, which will never be seen here.)
* Otherwise, anything in a baserel or joinrel targetlist ought to be a
* Var. Children of a partitioned table may have ConvertRowtypeExpr
* translating whole-row Var of a child to that of the parent. Children
* of an inherited table or subquery child rels can not directly
* participate in a join, so other kinds of nodes here.
*/
if (!IsA(var, Var))
if (IsA(var, Var))
{
baserel = find_base_rel(root, var->varno);
ndx = var->varattno - baserel->min_attr;
}
else if (IsA(var, ConvertRowtypeExpr))
{
ConvertRowtypeExpr *child_expr = (ConvertRowtypeExpr *) var;
Var *childvar = (Var *) child_expr->arg;
/*
* Child's whole-row references are converted to look like those
* of parent using ConvertRowtypeExpr. There can be as many
* ConvertRowtypeExpr decorations as the depth of partition tree.
* The argument to the deepest ConvertRowtypeExpr is expected to
* be a whole-row reference of the child.
*/
while (IsA(childvar, ConvertRowtypeExpr))
{
child_expr = (ConvertRowtypeExpr *) childvar;
childvar = (Var *) child_expr->arg;
}
Assert(IsA(childvar, Var) && childvar->varattno == 0);
baserel = find_base_rel(root, childvar->varno);
ndx = 0 - baserel->min_attr;
}
else
elog(ERROR, "unexpected node type in rel targetlist: %d",
(int) nodeTag(var));
/* Get the Var's original base rel */
baserel = find_base_rel(root, var->varno);
/* Is it still needed above this joinrel? */
ndx = var->varattno - baserel->min_attr;
/* Is the target expression still needed above this joinrel? */
if (bms_nonempty_difference(baserel->attr_needed[ndx], relids))
{
/* Yup, add it to the output */
joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs, var);
/* Vars have cost zero, so no need to adjust reltarget->cost */
/*
* Vars have cost zero, so no need to adjust reltarget->cost. Even
* if it's a ConvertRowtypeExpr, it will be computed only for the
* base relation, costing nothing for a join.
*/
joinrel->reltarget->width += baserel->attr_widths[ndx];
}
}
@ -876,6 +1059,9 @@ subbuild_joinrel_joinlist(RelOptInfo *joinrel,
{
ListCell *l;
/* Expected to be called only for join between parent relations. */
Assert(joinrel->reloptkind == RELOPT_JOINREL);
foreach(l, joininfo_list)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
@ -1399,3 +1585,165 @@ find_param_path_info(RelOptInfo *rel, Relids required_outer)
return NULL;
}
/*
* build_joinrel_partition_info
* If the two relations have same partitioning scheme, their join may be
* partitioned and will follow the same partitioning scheme as the joining
* relations. Set the partition scheme and partition key expressions in
* the join relation.
*/
static void
build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel,
RelOptInfo *inner_rel, List *restrictlist,
JoinType jointype)
{
int partnatts;
int cnt;
PartitionScheme part_scheme;
/* Nothing to do if partition-wise join technique is disabled. */
if (!enable_partition_wise_join)
{
Assert(!IS_PARTITIONED_REL(joinrel));
return;
}
/*
* We can only consider this join as an input to further partition-wise
* joins if (a) the input relations are partitioned, (b) the partition
* schemes match, and (c) we can identify an equi-join between the
* partition keys. Note that if it were possible for
* have_partkey_equi_join to return different answers for the same joinrel
* depending on which join ordering we try first, this logic would break.
* That shouldn't happen, though, because of the way the query planner
* deduces implied equalities and reorders the joins. Please see
* optimizer/README for details.
*/
if (!IS_PARTITIONED_REL(outer_rel) || !IS_PARTITIONED_REL(inner_rel) ||
outer_rel->part_scheme != inner_rel->part_scheme ||
!have_partkey_equi_join(outer_rel, inner_rel, jointype, restrictlist))
{
Assert(!IS_PARTITIONED_REL(joinrel));
return;
}
part_scheme = outer_rel->part_scheme;
Assert(REL_HAS_ALL_PART_PROPS(outer_rel) &&
REL_HAS_ALL_PART_PROPS(inner_rel));
/*
* For now, our partition matching algorithm can match partitions only
* when the partition bounds of the joining relations are exactly same.
* So, bail out otherwise.
*/
if (outer_rel->nparts != inner_rel->nparts ||
!partition_bounds_equal(part_scheme->partnatts,
part_scheme->parttyplen,
part_scheme->parttypbyval,
outer_rel->boundinfo, inner_rel->boundinfo))
{
Assert(!IS_PARTITIONED_REL(joinrel));
return;
}
/*
* This function will be called only once for each joinrel, hence it
* should not have partition scheme, partition bounds, partition key
* expressions and array for storing child relations set.
*/
Assert(!joinrel->part_scheme && !joinrel->partexprs &&
!joinrel->nullable_partexprs && !joinrel->part_rels &&
!joinrel->boundinfo);
/*
* Join relation is partitioned using the same partitioning scheme as the
* joining relations and has same bounds.
*/
joinrel->part_scheme = part_scheme;
joinrel->boundinfo = outer_rel->boundinfo;
joinrel->nparts = outer_rel->nparts;
partnatts = joinrel->part_scheme->partnatts;
joinrel->partexprs = (List **) palloc0(sizeof(List *) * partnatts);
joinrel->nullable_partexprs =
(List **) palloc0(sizeof(List *) *partnatts);
/*
* Construct partition keys for the join.
*
* An INNER join between two partitioned relations can be regarded as
* partitioned by either key expression. For example, A INNER JOIN B ON A.a =
* B.b can be regarded as partitioned on A.a or on B.b; they are equivalent.
*
* For a SEMI or ANTI join, the result can only be regarded as being
* partitioned in the same manner as the outer side, since the inner columns
* are not retained.
*
* An OUTER join like (A LEFT JOIN B ON A.a = B.b) may produce rows with
* B.b NULL. These rows may not fit the partitioning conditions imposed on
* B.b. Hence, strictly speaking, the join is not partitioned by B.b and
* thus partition keys of an OUTER join should include partition key
* expressions from the OUTER side only. However, because all
* commonly-used comparison operators are strict, the presence of nulls on
* the outer side doesn't cause any problem; they can't match anything at
* future join levels anyway. Therefore, we track two sets of expressions:
* those that authentically partition the relation (partexprs) and those
* that partition the relation with the exception that extra nulls may be
* present (nullable_partexprs). When the comparison operator is strict,
* the latter is just as good as the former.
*/
for (cnt = 0; cnt < partnatts; cnt++)
{
List *outer_expr;
List *outer_null_expr;
List *inner_expr;
List *inner_null_expr;
List *partexpr = NIL;
List *nullable_partexpr = NIL;
outer_expr = list_copy(outer_rel->partexprs[cnt]);
outer_null_expr = list_copy(outer_rel->nullable_partexprs[cnt]);
inner_expr = list_copy(inner_rel->partexprs[cnt]);
inner_null_expr = list_copy(inner_rel->nullable_partexprs[cnt]);
switch (jointype)
{
case JOIN_INNER:
partexpr = list_concat(outer_expr, inner_expr);
nullable_partexpr = list_concat(outer_null_expr,
inner_null_expr);
break;
case JOIN_SEMI:
case JOIN_ANTI:
partexpr = outer_expr;
nullable_partexpr = outer_null_expr;
break;
case JOIN_LEFT:
partexpr = outer_expr;
nullable_partexpr = list_concat(inner_expr,
outer_null_expr);
nullable_partexpr = list_concat(nullable_partexpr,
inner_null_expr);
break;
case JOIN_FULL:
nullable_partexpr = list_concat(outer_expr,
inner_expr);
nullable_partexpr = list_concat(nullable_partexpr,
outer_null_expr);
nullable_partexpr = list_concat(nullable_partexpr,
inner_null_expr);
break;
default:
elog(ERROR, "unrecognized join type: %d", (int) jointype);
}
joinrel->partexprs[cnt] = partexpr;
joinrel->nullable_partexprs[cnt] = nullable_partexpr;
}
}