aboutsummaryrefslogtreecommitdiff
path: root/src/backend/optimizer/util/relnode.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2023-01-30 13:16:20 -0500
committerTom Lane <tgl@sss.pgh.pa.us>2023-01-30 13:16:20 -0500
commit2489d76c4906f4461a364ca8ad7e0751ead8aa0d (patch)
tree145ebc28d5ea8f5a5ba340b9e353a11de786adae /src/backend/optimizer/util/relnode.c
parentec7e053a98f39a9e3c7e6d35f0d2e83933882399 (diff)
downloadpostgresql-2489d76c4906f4461a364ca8ad7e0751ead8aa0d.tar.gz
postgresql-2489d76c4906f4461a364ca8ad7e0751ead8aa0d.zip
Make Vars be outer-join-aware.
Traditionally we used the same Var struct to represent the value of a table column everywhere in parse and plan trees. This choice predates our support for SQL outer joins, and it's really a pretty bad idea with outer joins, because the Var's value can depend on where it is in the tree: it might go to NULL above an outer join. So expression nodes that are equal() per equalfuncs.c might not represent the same value, which is a huge correctness hazard for the planner. To improve this, decorate Var nodes with a bitmapset showing which outer joins (identified by RTE indexes) may have nulled them at the point in the parse tree where the Var appears. This allows us to trust that equal() Vars represent the same value. A certain amount of klugery is still needed to cope with cases where we re-order two outer joins, but it's possible to make it work without sacrificing that core principle. PlaceHolderVars receive similar decoration for the same reason. In the planner, we include these outer join bitmapsets into the relids that an expression is considered to depend on, and in consequence also add outer-join relids to the relids of join RelOptInfos. This allows us to correctly perceive whether an expression can be calculated above or below a particular outer join. This change affects FDWs that want to plan foreign joins. They *must* follow suit when labeling foreign joins in order to match with the core planner, but for many purposes (if postgres_fdw is any guide) they'd prefer to consider only base relations within the join. To support both requirements, redefine ForeignScan.fs_relids as base+OJ relids, and add a new field fs_base_relids that's set up by the core planner. Large though it is, this commit just does the minimum necessary to install the new mechanisms and get check-world passing again. Follow-up patches will perform some cleanup. (The README additions and comments mention some stuff that will appear in the follow-up.) Patch by me; thanks to Richard Guo for review. Discussion: https://postgr.es/m/830269.1656693747@sss.pgh.pa.us
Diffstat (limited to 'src/backend/optimizer/util/relnode.c')
-rw-r--r--src/backend/optimizer/util/relnode.c389
1 files changed, 339 insertions, 50 deletions
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 0a5632699d6..ebfb4ddd121 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
#include "optimizer/plancat.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
#include "parser/parse_relation.h"
#include "utils/hsearch.h"
#include "utils/lsyscache.h"
@@ -40,7 +41,9 @@ typedef struct JoinHashEntry
} JoinHashEntry;
static void build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
- RelOptInfo *input_rel);
+ RelOptInfo *input_rel,
+ SpecialJoinInfo *sjinfo,
+ bool can_null);
static List *build_joinrel_restrictlist(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outer_rel,
@@ -48,8 +51,10 @@ static List *build_joinrel_restrictlist(PlannerInfo *root,
static void build_joinrel_joinlist(RelOptInfo *joinrel,
RelOptInfo *outer_rel,
RelOptInfo *inner_rel);
-static List *subbuild_joinrel_restrictlist(RelOptInfo *joinrel,
- List *joininfo_list,
+static List *subbuild_joinrel_restrictlist(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *input_rel,
+ Relids both_input_relids,
List *new_restrictlist);
static List *subbuild_joinrel_joinlist(RelOptInfo *joinrel,
List *joininfo_list,
@@ -57,10 +62,12 @@ 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,
+static void build_joinrel_partition_info(PlannerInfo *root,
+ RelOptInfo *joinrel,
RelOptInfo *outer_rel, RelOptInfo *inner_rel,
- List *restrictlist, JoinType jointype);
-static bool have_partkey_equi_join(RelOptInfo *joinrel,
+ SpecialJoinInfo *sjinfo,
+ List *restrictlist);
+static bool have_partkey_equi_join(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *rel1, RelOptInfo *rel2,
JoinType jointype, List *restrictlist);
static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel,
@@ -373,7 +380,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
/*
* find_base_rel
- * Find a base or other relation entry, which must already exist.
+ * Find a base or otherrel relation entry, which must already exist.
*/
RelOptInfo *
find_base_rel(PlannerInfo *root, int relid)
@@ -395,6 +402,44 @@ find_base_rel(PlannerInfo *root, int relid)
}
/*
+ * find_base_rel_ignore_join
+ * Find a base or otherrel relation entry, which must already exist.
+ *
+ * Unlike find_base_rel, if relid references an outer join then this
+ * will return NULL rather than raising an error. This is convenient
+ * for callers that must deal with relid sets including both base and
+ * outer joins.
+ */
+RelOptInfo *
+find_base_rel_ignore_join(PlannerInfo *root, int relid)
+{
+ Assert(relid > 0);
+
+ if (relid < root->simple_rel_array_size)
+ {
+ RelOptInfo *rel;
+ RangeTblEntry *rte;
+
+ rel = root->simple_rel_array[relid];
+ if (rel)
+ return rel;
+
+ /*
+ * We could just return NULL here, but for debugging purposes it seems
+ * best to actually verify that the relid is an outer join and not
+ * something weird.
+ */
+ rte = root->simple_rte_array[relid];
+ if (rte && rte->rtekind == RTE_JOIN && rte->jointype != JOIN_INNER)
+ return NULL;
+ }
+
+ elog(ERROR, "no relation entry for relid %d", relid);
+
+ return NULL; /* keep compiler quiet */
+}
+
+/*
* build_join_rel_hash
* Construct the auxiliary hash table for join relations.
*/
@@ -692,9 +737,11 @@ build_join_rel(PlannerInfo *root,
* and inner rels we first try to build it from. But the contents should
* be the same regardless.
*/
- build_joinrel_tlist(root, joinrel, outer_rel);
- build_joinrel_tlist(root, joinrel, inner_rel);
- add_placeholders_to_joinrel(root, joinrel, outer_rel, inner_rel);
+ build_joinrel_tlist(root, joinrel, outer_rel, sjinfo,
+ (sjinfo->jointype == JOIN_FULL));
+ build_joinrel_tlist(root, joinrel, inner_rel, sjinfo,
+ (sjinfo->jointype != JOIN_INNER));
+ add_placeholders_to_joinrel(root, joinrel, outer_rel, inner_rel, sjinfo);
/*
* add_placeholders_to_joinrel also took care of adding the ph_lateral
@@ -726,8 +773,8 @@ 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);
+ build_joinrel_partition_info(root, joinrel, outer_rel, inner_rel, sjinfo,
+ restrictlist);
/*
* Set estimates of the joinrel's size.
@@ -783,16 +830,14 @@ build_join_rel(PlannerInfo *root,
* '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
- * 'jointype' is the join type (inner, left, full, etc)
+ * 'sjinfo': child join's join-type details
*/
RelOptInfo *
build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
RelOptInfo *inner_rel, RelOptInfo *parent_joinrel,
- List *restrictlist, SpecialJoinInfo *sjinfo,
- JoinType jointype)
+ List *restrictlist, SpecialJoinInfo *sjinfo)
{
RelOptInfo *joinrel = makeNode(RelOptInfo);
AppendRelInfo **appinfos;
@@ -806,6 +851,8 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
joinrel->reloptkind = RELOPT_OTHER_JOINREL;
joinrel->relids = bms_union(outer_rel->relids, inner_rel->relids);
+ if (sjinfo->ojrelid != 0)
+ joinrel->relids = bms_add_member(joinrel->relids, sjinfo->ojrelid);
joinrel->rows = 0;
/* cheap startup cost is interesting iff not all tuples to be retrieved */
joinrel->consider_startup = (root->tuple_fraction > 0);
@@ -892,8 +939,8 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
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);
+ build_joinrel_partition_info(root, joinrel, outer_rel, inner_rel, sjinfo,
+ restrictlist);
/* Child joinrel is parallel safe if parent is parallel safe. */
joinrel->consider_parallel = parent_joinrel->consider_parallel;
@@ -975,10 +1022,41 @@ min_join_parameterization(PlannerInfo *root,
*
* We also compute the expected width of the join's output, making use
* of data that was cached at the baserel level by set_rel_width().
+ *
+ * Pass can_null as true if the join is an outer join that can null Vars
+ * from this input relation. If so, we will (normally) add the join's relid
+ * to the nulling bitmaps of Vars and PHVs bubbled up from the input.
+ *
+ * When forming an outer join's target list, special handling is needed
+ * in case the outer join was commuted with another one per outer join
+ * identity 3 (see optimizer/README). We must take steps to ensure that
+ * the output Vars have the same nulling bitmaps that they would if the
+ * two joins had been done in syntactic order; else they won't match Vars
+ * appearing higher in the query tree. We need to do two things:
+ *
+ * First, sjinfo->commute_above_r is added to the nulling bitmaps of RHS Vars.
+ * This takes care of the case where we implement
+ * A leftjoin (B leftjoin C on (Pbc)) on (Pab)
+ * as
+ * (A leftjoin B on (Pab)) leftjoin C on (Pbc)
+ * The C columns emitted by the B/C join need to be shown as nulled by both
+ * the B/C and A/B joins, even though they've not traversed the A/B join.
+ * (If the joins haven't been commuted, we are adding the nullingrel bits
+ * prematurely; but that's okay because the C columns can't be referenced
+ * between here and the upper join.)
+ *
+ * Second, if a RHS Var has any of the relids in sjinfo->commute_above_l
+ * already set in its nulling bitmap, then we *don't* add sjinfo->ojrelid
+ * to its nulling bitmap (but we do still add commute_above_r). This takes
+ * care of the reverse transformation: if the original syntax was
+ * (A leftjoin B on (Pab)) leftjoin C on (Pbc)
+ * then the now-upper A/B join must not mark C columns as nulled by itself.
*/
static void
build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
- RelOptInfo *input_rel)
+ RelOptInfo *input_rel,
+ SpecialJoinInfo *sjinfo,
+ bool can_null)
{
Relids relids = joinrel->relids;
ListCell *vars;
@@ -998,7 +1076,24 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
/* Is it still needed above this joinrel? */
if (bms_nonempty_difference(phinfo->ph_needed, relids))
{
- /* Yup, add it to the output */
+ /*
+ * Yup, add it to the output. If this join potentially nulls
+ * this input, we have to update the PHV's phnullingrels,
+ * which means making a copy.
+ */
+ if (can_null)
+ {
+ phv = copyObject(phv);
+ /* See comments above to understand this logic */
+ if (sjinfo->ojrelid != 0 &&
+ !bms_overlap(phv->phnullingrels, sjinfo->commute_above_l))
+ phv->phnullingrels = bms_add_member(phv->phnullingrels,
+ sjinfo->ojrelid);
+ if (sjinfo->commute_above_r)
+ phv->phnullingrels = bms_add_members(phv->phnullingrels,
+ sjinfo->commute_above_r);
+ }
+
joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs,
phv);
/* Bubbling up the precomputed result has cost zero */
@@ -1022,9 +1117,7 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
RowIdentityVarInfo *ridinfo = (RowIdentityVarInfo *)
list_nth(root->row_identity_vars, var->varattno - 1);
- joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs,
- var);
- /* Vars have cost zero, so no need to adjust reltarget->cost */
+ /* Update reltarget width estimate from RowIdentityVarInfo */
joinrel->reltarget->width += ridinfo->rowidwidth;
}
else
@@ -1037,15 +1130,35 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
/* Is it still needed above this joinrel? */
ndx = var->varattno - baserel->min_attr;
- 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 */
- joinrel->reltarget->width += baserel->attr_widths[ndx];
- }
+ if (!bms_nonempty_difference(baserel->attr_needed[ndx], relids))
+ continue; /* nope, skip it */
+
+ /* Update reltarget width estimate from baserel's attr_widths */
+ joinrel->reltarget->width += baserel->attr_widths[ndx];
}
+
+ /*
+ * Add the Var to the output. If this join potentially nulls this
+ * input, we have to update the Var's varnullingrels, which means
+ * making a copy.
+ */
+ if (can_null)
+ {
+ var = copyObject(var);
+ /* See comments above to understand this logic */
+ if (sjinfo->ojrelid != 0 &&
+ !bms_overlap(var->varnullingrels, sjinfo->commute_above_l))
+ var->varnullingrels = bms_add_member(var->varnullingrels,
+ sjinfo->ojrelid);
+ if (sjinfo->commute_above_r)
+ var->varnullingrels = bms_add_members(var->varnullingrels,
+ sjinfo->commute_above_r);
+ }
+
+ joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs,
+ var);
+
+ /* Vars have cost zero, so no need to adjust reltarget->cost */
}
}
@@ -1064,7 +1177,7 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
* is not handled in the sub-relations, so it depends on which
* sub-relations are considered.
*
- * If a join clause from an input relation refers to base rels still not
+ * If a join clause from an input relation refers to base+OJ rels still not
* present in the joinrel, then it is still a join clause for the joinrel;
* we put it into the joininfo list for the joinrel. Otherwise,
* the clause is now a restrict clause for the joined relation, and we
@@ -1098,14 +1211,19 @@ build_joinrel_restrictlist(PlannerInfo *root,
RelOptInfo *inner_rel)
{
List *result;
+ Relids both_input_relids;
+
+ both_input_relids = bms_union(outer_rel->relids, inner_rel->relids);
/*
* Collect all the clauses that syntactically belong at this level,
* eliminating any duplicates (important since we will see many of the
* same clauses arriving from both input relations).
*/
- result = subbuild_joinrel_restrictlist(joinrel, outer_rel->joininfo, NIL);
- result = subbuild_joinrel_restrictlist(joinrel, inner_rel->joininfo, result);
+ result = subbuild_joinrel_restrictlist(root, joinrel, outer_rel,
+ both_input_relids, NIL);
+ result = subbuild_joinrel_restrictlist(root, joinrel, inner_rel,
+ both_input_relids, result);
/*
* Add on any clauses derived from EquivalenceClasses. These cannot be
@@ -1140,24 +1258,63 @@ build_joinrel_joinlist(RelOptInfo *joinrel,
}
static List *
-subbuild_joinrel_restrictlist(RelOptInfo *joinrel,
- List *joininfo_list,
+subbuild_joinrel_restrictlist(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *input_rel,
+ Relids both_input_relids,
List *new_restrictlist)
{
ListCell *l;
- foreach(l, joininfo_list)
+ foreach(l, input_rel->joininfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
if (bms_is_subset(rinfo->required_relids, joinrel->relids))
{
/*
- * This clause becomes a restriction clause for the joinrel, since
- * it refers to no outside rels. Add it to the list, being
- * careful to eliminate duplicates. (Since RestrictInfo nodes in
- * different joinlists will have been multiply-linked rather than
- * copied, pointer equality should be a sufficient test.)
+ * This clause should become a restriction clause for the joinrel,
+ * since it refers to no outside rels. However, if it's a clone
+ * clause then it might be too late to evaluate it, so we have to
+ * check. (If it is too late, just ignore the clause, taking it
+ * on faith that another clone was or will be selected.) Clone
+ * clauses should always be outer-join clauses, so we compare
+ * against both_input_relids.
+ */
+ if (rinfo->has_clone || rinfo->is_clone)
+ {
+ Assert(!RINFO_IS_PUSHED_DOWN(rinfo, joinrel->relids));
+ if (!bms_is_subset(rinfo->required_relids, both_input_relids))
+ continue;
+ if (!clause_is_computable_at(root, rinfo->clause_relids,
+ both_input_relids))
+ continue;
+ }
+ else
+ {
+ /*
+ * For non-clone clauses, we just Assert it's OK. These might
+ * be either join or filter clauses.
+ */
+#ifdef USE_ASSERT_CHECKING
+ if (RINFO_IS_PUSHED_DOWN(rinfo, joinrel->relids))
+ Assert(clause_is_computable_at(root, rinfo->clause_relids,
+ joinrel->relids));
+ else
+ {
+ Assert(bms_is_subset(rinfo->required_relids,
+ both_input_relids));
+ Assert(clause_is_computable_at(root, rinfo->clause_relids,
+ both_input_relids));
+ }
+#endif
+ }
+
+ /*
+ * OK, so add it to the list, being careful to eliminate
+ * duplicates. (Since RestrictInfo nodes in different joinlists
+ * will have been multiply-linked rather than copied, pointer
+ * equality should be a sufficient test.)
*/
new_restrictlist = list_append_unique_ptr(new_restrictlist, rinfo);
}
@@ -1319,6 +1476,7 @@ get_baserel_parampathinfo(PlannerInfo *root, RelOptInfo *baserel,
ParamPathInfo *ppi;
Relids joinrelids;
List *pclauses;
+ Bitmapset *pserials;
double rows;
ListCell *lc;
@@ -1361,6 +1519,15 @@ get_baserel_parampathinfo(PlannerInfo *root, RelOptInfo *baserel,
required_outer,
baserel));
+ /* Compute set of serial numbers of the enforced clauses */
+ pserials = NULL;
+ foreach(lc, pclauses)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ pserials = bms_add_member(pserials, rinfo->rinfo_serial);
+ }
+
/* Estimate the number of rows returned by the parameterized scan */
rows = get_parameterized_baserel_size(root, baserel, pclauses);
@@ -1369,6 +1536,7 @@ get_baserel_parampathinfo(PlannerInfo *root, RelOptInfo *baserel,
ppi->ppi_req_outer = required_outer;
ppi->ppi_rows = rows;
ppi->ppi_clauses = pclauses;
+ ppi->ppi_serials = pserials;
baserel->ppilist = lappend(baserel->ppilist, ppi);
return ppi;
@@ -1594,6 +1762,7 @@ get_joinrel_parampathinfo(PlannerInfo *root, RelOptInfo *joinrel,
ppi->ppi_req_outer = required_outer;
ppi->ppi_rows = rows;
ppi->ppi_clauses = NIL;
+ ppi->ppi_serials = NULL;
joinrel->ppilist = lappend(joinrel->ppilist, ppi);
return ppi;
@@ -1632,6 +1801,7 @@ get_appendrel_parampathinfo(RelOptInfo *appendrel, Relids required_outer)
ppi->ppi_req_outer = required_outer;
ppi->ppi_rows = 0;
ppi->ppi_clauses = NIL;
+ ppi->ppi_serials = NULL;
appendrel->ppilist = lappend(appendrel->ppilist, ppi);
return ppi;
@@ -1658,15 +1828,110 @@ find_param_path_info(RelOptInfo *rel, Relids required_outer)
}
/*
+ * get_param_path_clause_serials
+ * Given a parameterized Path, return the set of pushed-down clauses
+ * (identified by rinfo_serial numbers) enforced within the Path.
+ */
+Bitmapset *
+get_param_path_clause_serials(Path *path)
+{
+ if (path->param_info == NULL)
+ return NULL; /* not parameterized */
+ if (IsA(path, NestPath) ||
+ IsA(path, MergePath) ||
+ IsA(path, HashPath))
+ {
+ /*
+ * For a join path, combine clauses enforced within either input path
+ * with those enforced as joinrestrictinfo in this path. Note that
+ * joinrestrictinfo may include some non-pushed-down clauses, but for
+ * current purposes it's okay if we include those in the result. (To
+ * be more careful, we could check for clause_relids overlapping the
+ * path parameterization, but it's not worth the cycles for now.)
+ */
+ JoinPath *jpath = (JoinPath *) path;
+ Bitmapset *pserials;
+ ListCell *lc;
+
+ pserials = NULL;
+ pserials = bms_add_members(pserials,
+ get_param_path_clause_serials(jpath->outerjoinpath));
+ pserials = bms_add_members(pserials,
+ get_param_path_clause_serials(jpath->innerjoinpath));
+ foreach(lc, jpath->joinrestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ pserials = bms_add_member(pserials, rinfo->rinfo_serial);
+ }
+ return pserials;
+ }
+ else if (IsA(path, AppendPath))
+ {
+ /*
+ * For an appendrel, take the intersection of the sets of clauses
+ * enforced in each input path.
+ */
+ AppendPath *apath = (AppendPath *) path;
+ Bitmapset *pserials;
+ ListCell *lc;
+
+ pserials = NULL;
+ foreach(lc, apath->subpaths)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+ Bitmapset *subserials;
+
+ subserials = get_param_path_clause_serials(subpath);
+ if (lc == list_head(apath->subpaths))
+ pserials = bms_copy(subserials);
+ else
+ pserials = bms_int_members(pserials, subserials);
+ }
+ return pserials;
+ }
+ else if (IsA(path, MergeAppendPath))
+ {
+ /* Same as AppendPath case */
+ MergeAppendPath *apath = (MergeAppendPath *) path;
+ Bitmapset *pserials;
+ ListCell *lc;
+
+ pserials = NULL;
+ foreach(lc, apath->subpaths)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+ Bitmapset *subserials;
+
+ subserials = get_param_path_clause_serials(subpath);
+ if (lc == list_head(apath->subpaths))
+ pserials = bms_copy(subserials);
+ else
+ pserials = bms_int_members(pserials, subserials);
+ }
+ return pserials;
+ }
+ else
+ {
+ /*
+ * Otherwise, it's a baserel path and we can use the
+ * previously-computed set of serial numbers.
+ */
+ return path->param_info->ppi_serials;
+ }
+}
+
+/*
* build_joinrel_partition_info
* Checks if the two relations being joined can use partitionwise join
* and if yes, initialize partitioning information of the resulting
* partitioned join relation.
*/
static void
-build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel,
- RelOptInfo *inner_rel, List *restrictlist,
- JoinType jointype)
+build_joinrel_partition_info(PlannerInfo *root,
+ RelOptInfo *joinrel, RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel, SpecialJoinInfo *sjinfo,
+ List *restrictlist)
{
PartitionScheme part_scheme;
@@ -1692,8 +1957,8 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel,
!outer_rel->consider_partitionwise_join ||
!inner_rel->consider_partitionwise_join ||
outer_rel->part_scheme != inner_rel->part_scheme ||
- !have_partkey_equi_join(joinrel, outer_rel, inner_rel,
- jointype, restrictlist))
+ !have_partkey_equi_join(root, joinrel, outer_rel, inner_rel,
+ sjinfo->jointype, restrictlist))
{
Assert(!IS_PARTITIONED_REL(joinrel));
return;
@@ -1717,7 +1982,8 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel,
* child-join relations of the join relation in try_partitionwise_join().
*/
joinrel->part_scheme = part_scheme;
- set_joinrel_partition_key_exprs(joinrel, outer_rel, inner_rel, jointype);
+ set_joinrel_partition_key_exprs(joinrel, outer_rel, inner_rel,
+ sjinfo->jointype);
/*
* Set the consider_partitionwise_join flag.
@@ -1735,7 +2001,7 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel,
* partition keys.
*/
static bool
-have_partkey_equi_join(RelOptInfo *joinrel,
+have_partkey_equi_join(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *rel1, RelOptInfo *rel2,
JoinType jointype, List *restrictlist)
{
@@ -1801,6 +2067,24 @@ have_partkey_equi_join(RelOptInfo *joinrel,
strict_op = op_strict(opexpr->opno);
/*
+ * Vars appearing in the relation's partition keys will not have any
+ * varnullingrels, but those in expr1 and expr2 will if we're above
+ * outer joins that could null the respective rels. It's okay to
+ * match anyway, if the join operator is strict.
+ */
+ if (strict_op)
+ {
+ if (bms_overlap(rel1->relids, root->outer_join_rels))
+ expr1 = (Expr *) remove_nulling_relids((Node *) expr1,
+ root->outer_join_rels,
+ NULL);
+ if (bms_overlap(rel2->relids, root->outer_join_rels))
+ expr2 = (Expr *) remove_nulling_relids((Node *) expr2,
+ root->outer_join_rels,
+ NULL);
+ }
+
+ /*
* Only clauses referencing the partition keys are useful for
* partitionwise join.
*/
@@ -2012,7 +2296,12 @@ set_joinrel_partition_key_exprs(RelOptInfo *joinrel,
* partitionwise nesting of any outer join.) We assume no
* type coercions are needed to make the coalesce expressions,
* since columns of different types won't have gotten
- * classified as the same PartitionScheme.
+ * classified as the same PartitionScheme. Note that we
+ * intentionally leave out the varnullingrels decoration that
+ * would ordinarily appear on the Vars inside these
+ * CoalesceExprs, because have_partkey_equi_join will strip
+ * varnullingrels from the expressions it will compare to the
+ * partexprs.
*/
foreach(lc, list_concat_copy(outer_expr, outer_null_expr))
{