diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 8f4d8a5022..cfac1ea57b 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -3880,7 +3880,17 @@ get_relation_column_alias_ids(Var *node, RelOptInfo *foreignrel,
i = 1;
foreach(lc, foreignrel->reltarget->exprs)
{
- if (equal(lfirst(lc), (Node *) node))
+ Var *tlvar = (Var *) lfirst(lc);
+
+ /*
+ * As in setrefs.c, we match only on varno/varattno. Ideally there
+ * would be some cross-check on varnullingrels, but it's unclear what
+ * to do exactly; we don't have enough context to know what that value
+ * should be.
+ */
+ if (IsA(tlvar, Var) &&
+ tlvar->varno == node->varno &&
+ tlvar->varattno == node->varattno)
{
*colno = i;
return;
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index d56951153b..53f6e22f39 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -834,10 +834,7 @@ get_useful_ecs_for_relation(PlannerInfo *root, RelOptInfo *rel)
/* If this is a child rel, we must use the topmost parent rel to search. */
if (IS_OTHER_REL(rel))
- {
- Assert(!bms_is_empty(rel->top_parent_relids));
- relids = rel->top_parent_relids;
- }
+ relids = rel->top_parent->relids;
else
relids = rel->relids;
@@ -1513,13 +1510,13 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
/*
* Identify which user to do the remote access as. This should match what
* ExecCheckRTEPerms() does. In case of a join or aggregate, use the
- * lowest-numbered member RTE as a representative; we would get the same
- * result from any.
+ * lowest-numbered member base RTE as a representative; we would get the
+ * same result from any.
*/
if (fsplan->scan.scanrelid > 0)
rtindex = fsplan->scan.scanrelid;
else
- rtindex = bms_next_member(fsplan->fs_relids, -1);
+ rtindex = bms_next_member(fsplan->fs_base_relids, -1);
rte = exec_rt_fetch(rtindex, estate);
userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
@@ -2405,7 +2402,7 @@ find_modifytable_subplan(PlannerInfo *root,
{
ForeignScan *fscan = (ForeignScan *) subplan;
- if (bms_is_member(rtindex, fscan->fs_relids))
+ if (bms_is_member(rtindex, fscan->fs_base_relids))
return fscan;
}
@@ -2832,8 +2829,8 @@ postgresExplainForeignScan(ForeignScanState *node, ExplainState *es)
* that setrefs.c won't update the string when flattening the
* rangetable. To find out what rtoffset was applied, identify the
* minimum RT index appearing in the string and compare it to the
- * minimum member of plan->fs_relids. (We expect all the relids in
- * the join will have been offset by the same amount; the Asserts
+ * minimum member of plan->fs_base_relids. (We expect all the relids
+ * in the join will have been offset by the same amount; the Asserts
* below should catch it if that ever changes.)
*/
minrti = INT_MAX;
@@ -2850,7 +2847,7 @@ postgresExplainForeignScan(ForeignScanState *node, ExplainState *es)
else
ptr++;
}
- rtoffset = bms_next_member(plan->fs_relids, -1) - minrti;
+ rtoffset = bms_next_member(plan->fs_base_relids, -1) - minrti;
/* Now we can translate the string */
relations = makeStringInfo();
@@ -2865,7 +2862,7 @@ postgresExplainForeignScan(ForeignScanState *node, ExplainState *es)
char *refname;
rti += rtoffset;
- Assert(bms_is_member(rti, plan->fs_relids));
+ Assert(bms_is_member(rti, plan->fs_base_relids));
rte = rt_fetch(rti, es->rtable);
Assert(rte->rtekind == RTE_RELATION);
/* This logic should agree with explain.c's ExplainTargetRel */
@@ -5614,7 +5611,7 @@ foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
/* PlaceHolderInfo refers to parent relids, not child relids. */
relids = IS_OTHER_REL(joinrel) ?
- joinrel->top_parent_relids : joinrel->relids;
+ joinrel->top_parent->relids : joinrel->relids;
if (bms_is_subset(phinfo->ph_eval_at, relids) &&
bms_nonempty_difference(relids, phinfo->ph_eval_at))
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index d0b5951019..329affa30b 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -351,6 +351,17 @@ GetForeignJoinPaths(PlannerInfo *root,
it will supply at run time in the tuples it returns.
+
+
+ Beginning with PostgreSQL 16,
+ fs_relids includes the rangetable indexes
+ of outer joins, if any were involved in this join. The new field
+ fs_base_relids includes only base
+ relation indexes, and thus
+ mimics fs_relids's old semantics.
+
+
+
See for additional information.
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 5d1f7089da..7d58443b07 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1092,7 +1092,7 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
break;
case T_ForeignScan:
*rels_used = bms_add_members(*rels_used,
- ((ForeignScan *) plan)->fs_relids);
+ ((ForeignScan *) plan)->fs_base_relids);
break;
case T_CustomScan:
*rels_used = bms_add_members(*rels_used,
diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c
index 043bb83f55..2b37266b6a 100644
--- a/src/backend/executor/execScan.c
+++ b/src/backend/executor/execScan.c
@@ -325,7 +325,7 @@ ExecScanReScan(ScanState *node)
* all of them.
*/
if (IsA(node->ps.plan, ForeignScan))
- relids = ((ForeignScan *) node->ps.plan)->fs_relids;
+ relids = ((ForeignScan *) node->ps.plan)->fs_base_relids;
else if (IsA(node->ps.plan, CustomScan))
relids = ((CustomScan *) node->ps.plan)->custom_relids;
else
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index e9342097e5..97c8cd0711 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -179,6 +179,9 @@ make_one_rel(PlannerInfo *root, List *joinlist)
root->all_baserels = bms_add_member(root->all_baserels, brel->relid);
}
+ /* Now we can form the value of all_query_rels, too */
+ root->all_query_rels = bms_union(root->all_baserels, root->outer_join_rels);
+
/* Mark base rels as to whether we care about fast-start plans */
set_base_rel_consider_startup(root);
@@ -230,9 +233,9 @@ make_one_rel(PlannerInfo *root, List *joinlist)
rel = make_rel_from_joinlist(root, joinlist);
/*
- * The result should join all and only the query's base rels.
+ * The result should join all and only the query's base + outer-join rels.
*/
- Assert(bms_equal(rel->relids, root->all_baserels));
+ Assert(bms_equal(rel->relids, root->all_query_rels));
return rel;
}
@@ -558,7 +561,7 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
* (see grouping_planner).
*/
if (rel->reloptkind == RELOPT_BASEREL &&
- bms_membership(root->all_baserels) != BMS_SINGLETON)
+ bms_membership(root->all_query_rels) != BMS_SINGLETON)
generate_useful_gather_paths(root, rel, false);
/* Now find the cheapest of the paths for this rel */
@@ -879,7 +882,7 @@ set_tablesample_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *
* to support an uncommon usage of second-rate sampling methods. Instead,
* if there is a risk that the query might perform an unsafe join, just
* wrap the SampleScan in a Materialize node. We can check for joins by
- * counting the membership of all_baserels (note that this correctly
+ * counting the membership of all_query_rels (note that this correctly
* counts inheritance trees as single rels). If we're inside a subquery,
* we can't easily check whether a join might occur in the outer query, so
* just assume one is possible.
@@ -888,7 +891,7 @@ set_tablesample_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *
* so check repeatable_across_scans last, even though that's a bit odd.
*/
if ((root->query_level > 1 ||
- bms_membership(root->all_baserels) != BMS_SINGLETON) &&
+ bms_membership(root->all_query_rels) != BMS_SINGLETON) &&
!(GetTsmRoutine(rte->tablesample->tsmhandler)->repeatable_across_scans))
{
path = (Path *) create_material_path(rel, path);
diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index 06f836308d..6a5182c0d7 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -218,7 +218,7 @@ clauselist_selectivity_ext(PlannerInfo *root,
if (rinfo)
{
- ok = (bms_membership(rinfo->clause_relids) == BMS_SINGLETON) &&
+ ok = (rinfo->num_base_rels == 1) &&
(is_pseudo_constant_clause_relids(lsecond(expr->args),
rinfo->right_relids) ||
(varonleft = false,
@@ -579,30 +579,6 @@ find_single_rel_for_clauses(PlannerInfo *root, List *clauses)
return NULL; /* no clauses */
}
-/*
- * bms_is_subset_singleton
- *
- * Same result as bms_is_subset(s, bms_make_singleton(x)),
- * but a little faster and doesn't leak memory.
- *
- * Is this of use anywhere else? If so move to bitmapset.c ...
- */
-static bool
-bms_is_subset_singleton(const Bitmapset *s, int x)
-{
- switch (bms_membership(s))
- {
- case BMS_EMPTY_SET:
- return true;
- case BMS_SINGLETON:
- return bms_is_member(x, s);
- case BMS_MULTIPLE:
- return false;
- }
- /* can't get here... */
- return false;
-}
-
/*
* treat_as_join_clause -
* Decide whether an operator clause is to be handled by the
@@ -631,17 +607,20 @@ treat_as_join_clause(PlannerInfo *root, Node *clause, RestrictInfo *rinfo,
else
{
/*
- * Otherwise, it's a join if there's more than one relation used. We
- * can optimize this calculation if an rinfo was passed.
+ * Otherwise, it's a join if there's more than one base relation used.
+ * We can optimize this calculation if an rinfo was passed.
*
* XXX Since we know the clause is being evaluated at a join, the
* only way it could be single-relation is if it was delayed by outer
- * joins. Although we can make use of the restriction qual estimators
- * anyway, it seems likely that we ought to account for the
- * probability of injected nulls somehow.
+ * joins. We intentionally count only baserels here, not OJs that
+ * might be present in rinfo->clause_relids, so that we direct such
+ * cases to the restriction qual estimators not join estimators.
+ * Eventually some notice should be taken of the possibility of
+ * injected nulls, but we'll likely want to do that in the restriction
+ * estimators rather than starting to treat such cases as join quals.
*/
if (rinfo)
- return (bms_membership(rinfo->clause_relids) == BMS_MULTIPLE);
+ return (rinfo->num_base_rels > 1);
else
return (NumRelids(root, clause) > 1);
}
@@ -753,8 +732,7 @@ clause_selectivity_ext(PlannerInfo *root,
* considering a unique-ified case, so we only need one cache variable
* for all non-JOIN_INNER cases.
*/
- if (varRelid == 0 ||
- bms_is_subset_singleton(rinfo->clause_relids, varRelid))
+ if (varRelid == 0 || rinfo->num_base_rels <= 1)
{
/* Cacheable --- do we already have the result? */
if (jointype == JOIN_INNER)
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index fcc26b01a4..3ca598830e 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -5083,7 +5083,9 @@ compute_semi_anti_join_factors(PlannerInfo *root,
norm_sjinfo.syn_lefthand = outerrel->relids;
norm_sjinfo.syn_righthand = innerrel->relids;
norm_sjinfo.jointype = JOIN_INNER;
+ norm_sjinfo.ojrelid = 0;
/* we don't bother trying to make the remaining fields valid */
+ norm_sjinfo.strict_relids = NULL;
norm_sjinfo.lhs_strict = false;
norm_sjinfo.delay_upper_joins = false;
norm_sjinfo.semi_can_btree = false;
@@ -5248,7 +5250,9 @@ approx_tuple_count(PlannerInfo *root, JoinPath *path, List *quals)
sjinfo.syn_lefthand = path->outerjoinpath->parent->relids;
sjinfo.syn_righthand = path->innerjoinpath->parent->relids;
sjinfo.jointype = JOIN_INNER;
+ sjinfo.ojrelid = 0;
/* we don't bother trying to make the remaining fields valid */
+ sjinfo.strict_relids = NULL;
sjinfo.lhs_strict = false;
sjinfo.delay_upper_joins = false;
sjinfo.semi_can_btree = false;
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 60c0e3f108..257ac1273c 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -29,6 +29,7 @@
#include "optimizer/paths.h"
#include "optimizer/planmain.h"
#include "optimizer/restrictinfo.h"
+#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
@@ -64,7 +65,7 @@ static bool reconsider_outer_join_clause(PlannerInfo *root,
RestrictInfo *rinfo,
bool outer_on_left);
static bool reconsider_full_join_clause(PlannerInfo *root,
- RestrictInfo *rinfo);
+ FullJoinClauseInfo *fjinfo);
static Bitmapset *get_eclass_indexes_for_relids(PlannerInfo *root,
Relids relids);
static Bitmapset *get_common_eclass_indexes(PlannerInfo *root, Relids relids1,
@@ -768,6 +769,9 @@ get_eclass_for_sort_expr(PlannerInfo *root,
{
RelOptInfo *rel = root->simple_rel_array[i];
+ if (rel == NULL)
+ continue; /* must be an outer join */
+
Assert(rel->reloptkind == RELOPT_BASEREL ||
rel->reloptkind == RELOPT_DEADREL);
@@ -936,7 +940,36 @@ is_exprlist_member(Expr *node, List *exprs)
if (expr && IsA(expr, TargetEntry))
expr = ((TargetEntry *) expr)->expr;
- if (equal(node, expr))
+ /*
+ * For Vars and PlaceHolderVars, match using the same rules as
+ * setrefs.c will, in particular ignoring nullingrels. XXX when that
+ * gets tightened up, this should too.
+ */
+ if (IsA(node, Var))
+ {
+ if (expr && IsA(expr, Var))
+ {
+ Var *v1 = (Var *) node;
+ Var *v2 = (Var *) expr;
+
+ if (v1->varno == v2->varno &&
+ v1->varattno == v2->varattno &&
+ v1->varlevelsup == v2->varlevelsup)
+ return true;
+ }
+ }
+ else if (IsA(node, PlaceHolderVar))
+ {
+ if (expr && IsA(expr, PlaceHolderVar))
+ {
+ PlaceHolderVar *v1 = (PlaceHolderVar *) node;
+ PlaceHolderVar *v2 = (PlaceHolderVar *) expr;
+
+ if (v1->phid == v2->phid)
+ return true;
+ }
+ }
+ else if (equal(node, expr))
return true;
}
return false;
@@ -1124,6 +1157,9 @@ generate_base_implied_equalities(PlannerInfo *root)
{
RelOptInfo *rel = root->simple_rel_array[i];
+ if (rel == NULL)
+ continue; /* must be an outer join */
+
Assert(rel->reloptkind == RELOPT_BASEREL);
rel->eclass_indexes = bms_add_member(rel->eclass_indexes,
@@ -1415,10 +1451,10 @@ generate_join_implied_equalities(PlannerInfo *root,
/* If inner rel is a child, extra setup work is needed */
if (IS_OTHER_REL(inner_rel))
{
- Assert(!bms_is_empty(inner_rel->top_parent_relids));
+ Assert(inner_rel->top_parent != NULL);
/* Fetch relid set for the topmost parent rel */
- nominal_inner_relids = inner_rel->top_parent_relids;
+ nominal_inner_relids = inner_rel->top_parent->relids;
/* ECs will be marked with the parent's relid, not the child's */
nominal_join_relids = bms_union(outer_relids, nominal_inner_relids);
}
@@ -1493,10 +1529,10 @@ generate_join_implied_equalities_for_ecs(PlannerInfo *root,
/* If inner rel is a child, extra setup work is needed */
if (IS_OTHER_REL(inner_rel))
{
- Assert(!bms_is_empty(inner_rel->top_parent_relids));
+ Assert(inner_rel->top_parent != NULL);
/* Fetch relid set for the topmost parent rel */
- nominal_inner_relids = inner_rel->top_parent_relids;
+ nominal_inner_relids = inner_rel->top_parent->relids;
/* ECs will be marked with the parent's relid, not the child's */
nominal_join_relids = bms_union(outer_relids, nominal_inner_relids);
}
@@ -1760,8 +1796,8 @@ generate_join_implied_equalities_broken(PlannerInfo *root,
if (IS_OTHER_REL(inner_rel) && result != NIL)
result = (List *) adjust_appendrel_attrs_multilevel(root,
(Node *) result,
- inner_rel->relids,
- inner_rel->top_parent_relids);
+ inner_rel,
+ inner_rel->top_parent);
return result;
}
@@ -2014,10 +2050,12 @@ reconsider_outer_join_clauses(PlannerInfo *root)
/* Process the FULL JOIN clauses */
foreach(cell, root->full_join_clauses)
{
- RestrictInfo *rinfo = (RestrictInfo *) lfirst(cell);
+ FullJoinClauseInfo *fjinfo = (FullJoinClauseInfo *) lfirst(cell);
- if (reconsider_full_join_clause(root, rinfo))
+ if (reconsider_full_join_clause(root, fjinfo))
{
+ RestrictInfo *rinfo = fjinfo->rinfo;
+
found = true;
/* remove it from the list */
root->full_join_clauses =
@@ -2046,9 +2084,9 @@ reconsider_outer_join_clauses(PlannerInfo *root)
}
foreach(cell, root->full_join_clauses)
{
- RestrictInfo *rinfo = (RestrictInfo *) lfirst(cell);
+ FullJoinClauseInfo *fjinfo = (FullJoinClauseInfo *) lfirst(cell);
- distribute_restrictinfo_to_rels(root, rinfo);
+ distribute_restrictinfo_to_rels(root, fjinfo->rinfo);
}
}
@@ -2184,8 +2222,11 @@ reconsider_outer_join_clause(PlannerInfo *root, RestrictInfo *rinfo,
* Returns true if we were able to propagate a constant through the clause.
*/
static bool
-reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo)
+reconsider_full_join_clause(PlannerInfo *root, FullJoinClauseInfo *fjinfo)
{
+ RestrictInfo *rinfo = fjinfo->rinfo;
+ SpecialJoinInfo *sjinfo = fjinfo->sjinfo;
+ Relids fjrelids = bms_make_singleton(sjinfo->ojrelid);
Expr *leftvar;
Expr *rightvar;
Oid opno,
@@ -2267,6 +2308,18 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo)
cfirst = (Node *) linitial(cexpr->args);
csecond = (Node *) lsecond(cexpr->args);
+ /*
+ * The COALESCE arguments will be marked as possibly nulled by
+ * the full join, while we wish to generate clauses that apply
+ * to the join's inputs. So we must strip the join from the
+ * nullingrels fields of cfirst/csecond before comparing them
+ * to leftvar/rightvar. (Perhaps with a less hokey
+ * representation for FULL JOIN USING output columns, this
+ * wouldn't be needed?)
+ */
+ cfirst = remove_nulling_relids(cfirst, fjrelids, NULL);
+ csecond = remove_nulling_relids(csecond, fjrelids, NULL);
+
if (equal(leftvar, cfirst) && equal(rightvar, csecond))
{
coal_idx = foreach_current_index(lc2);
@@ -2552,7 +2605,7 @@ add_child_rel_equivalences(PlannerInfo *root,
RelOptInfo *parent_rel,
RelOptInfo *child_rel)
{
- Relids top_parent_relids = child_rel->top_parent_relids;
+ Relids top_parent_relids = child_rel->top_parent->relids;
Relids child_relids = child_rel->relids;
int i;
@@ -2626,8 +2679,8 @@ add_child_rel_equivalences(PlannerInfo *root,
child_expr = (Expr *)
adjust_appendrel_attrs_multilevel(root,
(Node *) cur_em->em_expr,
- child_relids,
- top_parent_relids);
+ child_rel,
+ child_rel->top_parent);
}
/*
@@ -2680,7 +2733,7 @@ add_child_join_rel_equivalences(PlannerInfo *root,
RelOptInfo *parent_joinrel,
RelOptInfo *child_joinrel)
{
- Relids top_parent_relids = child_joinrel->top_parent_relids;
+ Relids top_parent_relids = child_joinrel->top_parent->relids;
Relids child_relids = child_joinrel->relids;
Bitmapset *matching_ecs;
MemoryContext oldcontext;
@@ -2768,8 +2821,8 @@ add_child_join_rel_equivalences(PlannerInfo *root,
child_expr = (Expr *)
adjust_appendrel_attrs_multilevel(root,
(Node *) cur_em->em_expr,
- child_relids,
- top_parent_relids);
+ child_joinrel,
+ child_joinrel->top_parent);
}
/*
@@ -2791,8 +2844,8 @@ add_child_join_rel_equivalences(PlannerInfo *root,
new_nullable_relids =
adjust_child_relids_multilevel(root,
new_nullable_relids,
- child_relids,
- top_parent_relids);
+ child_joinrel,
+ child_joinrel->top_parent);
(void) add_eq_member(cur_ec, child_expr,
new_relids, new_nullable_relids,
@@ -3094,10 +3147,7 @@ eclass_useful_for_merging(PlannerInfo *root,
/* If specified rel is a child, we must consider the topmost parent rel */
if (IS_OTHER_REL(rel))
- {
- Assert(!bms_is_empty(rel->top_parent_relids));
- relids = rel->top_parent_relids;
- }
+ relids = rel->top_parent->relids;
else
relids = rel->relids;
@@ -3203,6 +3253,8 @@ get_eclass_indexes_for_relids(PlannerInfo *root, Relids relids)
{
RelOptInfo *rel = root->simple_rel_array[i];
+ if (rel == NULL)
+ continue; /* must be an outer join */
ec_indexes = bms_add_members(ec_indexes, rel->eclass_indexes);
}
return ec_indexes;
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 0ef70ad7f1..ba451f8952 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -3357,13 +3357,13 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel)
* Add on any equivalence-derivable join clauses. Computing the correct
* relid sets for generate_join_implied_equalities is slightly tricky
* because the rel could be a child rel rather than a true baserel, and in
- * that case we must remove its parents' relid(s) from all_baserels.
+ * that case we must subtract its parents' relid(s) from all_query_rels.
*/
if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
- otherrels = bms_difference(root->all_baserels,
+ otherrels = bms_difference(root->all_query_rels,
find_childrel_parents(root, rel));
else
- otherrels = bms_difference(root->all_baserels, rel->relids);
+ otherrels = bms_difference(root->all_query_rels, rel->relids);
if (!bms_is_empty(otherrels))
clauselist =
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 2a3f0ab7bf..855948f192 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -34,8 +34,8 @@ set_join_pathlist_hook_type set_join_pathlist_hook = NULL;
* any of its child.
*/
#define PATH_PARAM_BY_PARENT(path, rel) \
- ((path)->param_info && bms_overlap(PATH_REQ_OUTER(path), \
- (rel)->top_parent_relids))
+ ((path)->param_info && (rel)->top_parent && \
+ 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))
@@ -141,7 +141,7 @@ add_paths_to_joinrel(PlannerInfo *root,
* partitions.
*/
if (joinrel->reloptkind == RELOPT_OTHER_JOINREL)
- joinrelids = joinrel->top_parent_relids;
+ joinrelids = joinrel->top_parent->relids;
else
joinrelids = joinrel->relids;
@@ -250,7 +250,7 @@ add_paths_to_joinrel(PlannerInfo *root,
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,
+ bms_difference(root->all_query_rels,
sjinfo2->min_righthand));
/* full joins constrain both sides symmetrically */
@@ -258,7 +258,7 @@ add_paths_to_joinrel(PlannerInfo *root,
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,
+ bms_difference(root->all_query_rels,
sjinfo2->min_lefthand));
}
@@ -643,13 +643,13 @@ try_nestloop_path(PlannerInfo *root,
* 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;
+ if (innerrel->top_parent)
+ innerrelids = innerrel->top_parent->relids;
else
innerrelids = innerrel->relids;
- if (outerrel->top_parent_relids)
- outerrelids = outerrel->top_parent_relids;
+ if (outerrel->top_parent)
+ outerrelids = outerrel->top_parent->relids;
else
outerrelids = outerrel->relids;
@@ -762,8 +762,8 @@ try_partial_nestloop_path(PlannerInfo *root,
* level parents, not the child relations, so we must use those relids
* for our parameterization tests.
*/
- if (outerrel->top_parent_relids)
- outerrelids = outerrel->top_parent_relids;
+ if (outerrel->top_parent)
+ outerrelids = outerrel->top_parent->relids;
else
outerrelids = outerrel->relids;
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 9da3ff2f9a..b64c37f089 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -353,7 +353,10 @@ make_rels_by_clauseless_joins(PlannerInfo *root,
*
* Caller must supply not only the two rels, but the union of their relids.
* (We could simplify the API by computing joinrelids locally, but this
- * would be redundant work in the normal path through make_join_rel.)
+ * would be redundant work in the normal path through make_join_rel.
+ * Note that this value does NOT include the RT index of any outer join that
+ * might need to be performed here, so it's not the canonical identifier
+ * of the join relation.)
*
* On success, *sjinfo_p is set to NULL if this is to be a plain inner join,
* else it's set to point to the associated SpecialJoinInfo node. Also,
@@ -695,7 +698,7 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
/* We should never try to join two overlapping sets of rels. */
Assert(!bms_overlap(rel1->relids, rel2->relids));
- /* Construct Relids set that identifies the joinrel. */
+ /* Construct Relids set that identifies the joinrel (without OJ as yet). */
joinrelids = bms_union(rel1->relids, rel2->relids);
/* Check validity and determine join type. */
@@ -707,6 +710,10 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
return NULL;
}
+ /* If we have an outer join, add its RTI to form the canonical relids. */
+ if (sjinfo && sjinfo->ojrelid != 0)
+ joinrelids = bms_add_member(joinrelids, sjinfo->ojrelid);
+
/* Swap rels if needed to match the join info. */
if (reversed)
{
@@ -730,7 +737,9 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
sjinfo->syn_lefthand = rel1->relids;
sjinfo->syn_righthand = rel2->relids;
sjinfo->jointype = JOIN_INNER;
+ sjinfo->ojrelid = 0;
/* we don't bother trying to make the remaining fields valid */
+ sjinfo->strict_relids = NULL;
sjinfo->lhs_strict = false;
sjinfo->delay_upper_joins = false;
sjinfo->semi_can_btree = false;
@@ -1510,8 +1519,6 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
/* 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
@@ -1521,6 +1528,15 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
child_rel1->relids,
child_rel2->relids);
+ /* Build correct join relids for child join */
+ child_joinrelids = bms_union(child_rel1->relids, child_rel2->relids);
+ if (child_sjinfo->ojrelid != 0)
+ child_joinrelids = bms_add_member(child_joinrelids,
+ child_sjinfo->ojrelid);
+
+ /* Find the AppendRelInfo structures */
+ appinfos = find_appinfos_by_relids(root, child_joinrelids, &nappinfos);
+
/*
* Construct restrictions applicable to the child join from those
* applicable to the parent join.
@@ -1536,8 +1552,7 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
{
child_joinrel = build_child_join_rel(root, child_rel1, child_rel2,
joinrel, child_restrictlist,
- child_sjinfo,
- child_sjinfo->jointype);
+ child_sjinfo);
joinrel->part_rels[cnt_parts] = child_joinrel;
joinrel->live_parts = bms_add_member(joinrel->live_parts, cnt_parts);
joinrel->all_partrels = bms_add_members(joinrel->all_partrels,
diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index 337f470d58..bbe31c03fe 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -34,7 +34,7 @@
/* local functions */
static bool join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo);
-static void remove_rel_from_query(PlannerInfo *root, int relid,
+static void remove_rel_from_query(PlannerInfo *root, int relid, int ojrelid,
Relids joinrelids);
static List *remove_rel_from_joinlist(List *joinlist, int relid, int *nremoved);
static bool rel_supports_distinctness(PlannerInfo *root, RelOptInfo *rel);
@@ -70,6 +70,7 @@ restart:
foreach(lc, root->join_info_list)
{
SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc);
+ Relids joinrelids;
int innerrelid;
int nremoved;
@@ -84,9 +85,12 @@ restart:
*/
innerrelid = bms_singleton_member(sjinfo->min_righthand);
- remove_rel_from_query(root, innerrelid,
- bms_union(sjinfo->min_lefthand,
- sjinfo->min_righthand));
+ /* Compute the relid set for the join we are considering */
+ joinrelids = bms_union(sjinfo->min_lefthand, sjinfo->min_righthand);
+ if (sjinfo->ojrelid != 0)
+ joinrelids = bms_add_member(joinrelids, sjinfo->ojrelid);
+
+ remove_rel_from_query(root, innerrelid, sjinfo->ojrelid, joinrelids);
/* We verify that exactly one reference gets removed from joinlist */
nremoved = 0;
@@ -188,6 +192,8 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo)
/* Compute the relid set for the join we are considering */
joinrelids = bms_union(sjinfo->min_lefthand, sjinfo->min_righthand);
+ if (sjinfo->ojrelid != 0)
+ joinrelids = bms_add_member(joinrelids, sjinfo->ojrelid);
/*
* We can't remove the join if any inner-rel attributes are used above the
@@ -306,10 +312,12 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo)
* no longer treated as a baserel, and that attributes of other baserels
* are no longer marked as being needed at joins involving this rel.
* Also, join quals involving the rel have to be removed from the joininfo
- * lists, but only if they belong to the outer join identified by joinrelids.
+ * lists, but only if they belong to the outer join identified by ojrelid
+ * and joinrelids.
*/
static void
-remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids)
+remove_rel_from_query(PlannerInfo *root, int relid, int ojrelid,
+ Relids joinrelids)
{
RelOptInfo *rel = find_base_rel(root, relid);
List *joininfos;
@@ -349,6 +357,13 @@ remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids)
}
}
+ /*
+ * The removed outer join has to be dropped from root->outer_join_rels.
+ * (We'd need to update all_baserels and all_query_rels too, but those
+ * haven't been computed yet.)
+ */
+ root->outer_join_rels = bms_del_member(root->outer_join_rels, ojrelid);
+
/*
* Likewise remove references from SpecialJoinInfo data structures.
*
@@ -365,6 +380,10 @@ remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids)
sjinfo->min_righthand = bms_del_member(sjinfo->min_righthand, relid);
sjinfo->syn_lefthand = bms_del_member(sjinfo->syn_lefthand, relid);
sjinfo->syn_righthand = bms_del_member(sjinfo->syn_righthand, relid);
+ sjinfo->min_lefthand = bms_del_member(sjinfo->min_lefthand, ojrelid);
+ sjinfo->min_righthand = bms_del_member(sjinfo->min_righthand, ojrelid);
+ sjinfo->syn_lefthand = bms_del_member(sjinfo->syn_lefthand, ojrelid);
+ sjinfo->syn_righthand = bms_del_member(sjinfo->syn_righthand, ojrelid);
}
/*
@@ -393,8 +412,10 @@ remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids)
else
{
phinfo->ph_eval_at = bms_del_member(phinfo->ph_eval_at, relid);
+ phinfo->ph_eval_at = bms_del_member(phinfo->ph_eval_at, ojrelid);
Assert(!bms_is_empty(phinfo->ph_eval_at));
phinfo->ph_needed = bms_del_member(phinfo->ph_needed, relid);
+ phinfo->ph_needed = bms_del_member(phinfo->ph_needed, ojrelid);
}
}
@@ -431,6 +452,8 @@ remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids)
rinfo->required_relids = bms_copy(rinfo->required_relids);
rinfo->required_relids = bms_del_member(rinfo->required_relids,
relid);
+ rinfo->required_relids = bms_del_member(rinfo->required_relids,
+ ojrelid);
distribute_restrictinfo_to_rels(root, rinfo);
}
}
@@ -545,6 +568,7 @@ reduce_unique_semijoins(PlannerInfo *root)
/* Compute the relid set for the join we are considering */
joinrelids = bms_union(sjinfo->min_lefthand, sjinfo->min_righthand);
+ Assert(sjinfo->ojrelid == 0); /* SEMI joins don't have RT indexes */
/*
* Since we're only considering a single-rel RHS, any join clauses it
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 76606faa3e..16247dd9ce 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -29,6 +29,7 @@
#include "optimizer/cost.h"
#include "optimizer/optimizer.h"
#include "optimizer/paramassign.h"
+#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/placeholder.h"
#include "optimizer/plancat.h"
@@ -4106,6 +4107,8 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
Index scan_relid = rel->relid;
Oid rel_oid = InvalidOid;
Plan *outer_plan = NULL;
+ Relids fs_base_relids;
+ int rtindex;
Assert(rel->fdwroutine != NULL);
@@ -4154,14 +4157,28 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
/*
* Likewise, copy the relids that are represented by this foreign scan. An
- * upper rel doesn't have relids set, but it covers all the base relations
- * participating in the underlying scan, so use root's all_baserels.
+ * upper rel doesn't have relids set, but it covers all the relations
+ * participating in the underlying scan/join, so use root->all_query_rels.
*/
if (rel->reloptkind == RELOPT_UPPER_REL)
- scan_plan->fs_relids = root->all_baserels;
+ scan_plan->fs_relids = root->all_query_rels;
else
scan_plan->fs_relids = best_path->path.parent->relids;
+ /*
+ * Join relid sets include relevant outer joins, but FDWs may need to know
+ * which are the included base rels. That's a bit tedious to get without
+ * access to the plan-time data structures, so compute it here.
+ */
+ fs_base_relids = NULL;
+ rtindex = -1;
+ while ((rtindex = bms_next_member(scan_plan->fs_relids, rtindex)) >= 0)
+ {
+ if (find_base_rel_ignore_join(root, rtindex) != NULL)
+ fs_base_relids = bms_add_member(fs_base_relids, rtindex);
+ }
+ scan_plan->fs_base_relids = fs_base_relids;
+
/*
* If this is a foreign join, and to make it valid to push down we had to
* assume that the current user is the same as some user explicitly named
@@ -5806,8 +5823,9 @@ make_foreignscan(List *qptlist,
node->fdw_private = fdw_private;
node->fdw_scan_tlist = fdw_scan_tlist;
node->fdw_recheck_quals = fdw_recheck_quals;
- /* fs_relids will be filled in by create_foreignscan_plan */
+ /* fs_relids, fs_base_relids will be filled by create_foreignscan_plan */
node->fs_relids = NULL;
+ node->fs_base_relids = NULL;
/* fsSystemCol will be filled in by create_foreignscan_plan */
node->fsSystemCol = false;
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 023efbaf09..31911ecc42 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -60,12 +60,15 @@ static void process_security_barrier_quals(PlannerInfo *root,
static SpecialJoinInfo *make_outerjoininfo(PlannerInfo *root,
Relids left_rels, Relids right_rels,
Relids inner_join_rels,
- JoinType jointype, List *clause);
+ JoinType jointype, Index ojrelid,
+ List *clause);
static void compute_semijoin_info(PlannerInfo *root, SpecialJoinInfo *sjinfo,
List *clause);
+static List *remove_unneeded_nulling_relids(PlannerInfo *root, List *quals,
+ SpecialJoinInfo *sjinfo);
static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
bool below_outer_join,
- JoinType jointype,
+ SpecialJoinInfo *sjinfo,
Index security_level,
Relids qualscope,
Relids ojscope,
@@ -250,10 +253,16 @@ add_vars_to_targetlist(PlannerInfo *root, List *vars,
attno -= rel->min_attr;
if (rel->attr_needed[attno] == NULL)
{
- /* Variable not yet requested, so add to rel's targetlist */
- /* XXX is copyObject necessary here? */
- rel->reltarget->exprs = lappend(rel->reltarget->exprs,
- copyObject(var));
+ /*
+ * Variable not yet requested, so add to rel's targetlist.
+ *
+ * The value available at the rel's scan level has not been
+ * nulled by any outer join, so drop its varnullingrels.
+ * (We'll put those back as we climb up the join tree.)
+ */
+ var = copyObject(var);
+ var->varnullingrels = NULL;
+ rel->reltarget->exprs = lappend(rel->reltarget->exprs, var);
/* reltarget cost and width will be computed later */
}
rel->attr_needed[attno] = bms_add_members(rel->attr_needed[attno],
@@ -551,8 +560,10 @@ create_lateral_join_info(PlannerInfo *root)
varno = -1;
while ((varno = bms_next_member(eval_at, varno)) >= 0)
{
- RelOptInfo *brel = find_base_rel(root, varno);
+ RelOptInfo *brel = find_base_rel_ignore_join(root, varno);
+ if (brel == NULL)
+ continue; /* ignore outer joins in eval_at */
brel->lateral_relids = bms_add_members(brel->lateral_relids,
phinfo->ph_lateral);
}
@@ -643,7 +654,10 @@ create_lateral_join_info(PlannerInfo *root)
{
RelOptInfo *brel2 = root->simple_rel_array[rti2];
- Assert(brel2 != NULL && brel2->reloptkind == RELOPT_BASEREL);
+ if (brel2 == NULL)
+ continue; /* must be an OJ */
+
+ Assert(brel2->reloptkind == RELOPT_BASEREL);
brel2->lateral_referencers =
bms_add_member(brel2->lateral_referencers, rti);
}
@@ -695,7 +709,8 @@ deconstruct_jointree(PlannerInfo *root)
Assert(root->parse->jointree != NULL &&
IsA(root->parse->jointree, FromExpr));
- /* this is filled as we scan the jointree */
+ /* These are filled as we scan the jointree */
+ root->outer_join_rels = NULL;
root->nullable_baserels = NULL;
result = deconstruct_recurse(root, (Node *) root->parse->jointree, false,
@@ -717,7 +732,7 @@ deconstruct_jointree(PlannerInfo *root)
* below_outer_join is true if this node is within the nullable side of a
* higher-level outer join
* Outputs:
- * *qualscope gets the set of base Relids syntactically included in this
+ * *qualscope gets the set of base+OJ Relids syntactically included in this
* jointree node (do not modify or free this, as it may also be pointed
* to by RestrictInfo and SpecialJoinInfo nodes)
* *inner_join_rels gets the set of base Relids syntactically included in
@@ -802,6 +817,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
* there was exactly one element, we should (and already did) report
* whatever its inner_join_rels were. If there were no elements (is
* that still possible?) the initialization before the loop fixed it.
+ *
+ * XXX now wrong, do we care?
*/
if (list_length(f->fromlist) > 1)
*inner_join_rels = *qualscope;
@@ -816,7 +833,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
if (bms_is_subset(pq->relids, *qualscope))
distribute_qual_to_rels(root, pq->qual,
- below_outer_join, JOIN_INNER,
+ below_outer_join, NULL,
root->qual_security_level,
*qualscope, NULL, NULL,
NULL);
@@ -832,7 +849,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
Node *qual = (Node *) lfirst(l);
distribute_qual_to_rels(root, qual,
- below_outer_join, JOIN_INNER,
+ below_outer_join, NULL,
root->qual_security_level,
*qualscope, NULL, NULL,
postponed_qual_list);
@@ -896,6 +913,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
&rightids, &right_inners,
&child_postponed_quals);
*qualscope = bms_union(leftids, rightids);
+ /* caution: ANTI join derived from SEMI will lack rtindex */
+ if (j->rtindex != 0)
+ {
+ *qualscope = bms_add_member(*qualscope, j->rtindex);
+ root->outer_join_rels = bms_add_member(root->outer_join_rels,
+ j->rtindex);
+ }
*inner_join_rels = bms_union(left_inners, right_inners);
nonnullable_rels = leftids;
nullable_rels = rightids;
@@ -910,6 +934,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
&rightids, &right_inners,
&child_postponed_quals);
*qualscope = bms_union(leftids, rightids);
+ /* SEMI join never has rtindex, so don't add to qualscope */
+ Assert(j->rtindex == 0);
*inner_join_rels = bms_union(left_inners, right_inners);
/* Semi join adds no restrictions for quals */
nonnullable_rels = NULL;
@@ -931,6 +957,10 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
&rightids, &right_inners,
&child_postponed_quals);
*qualscope = bms_union(leftids, rightids);
+ Assert(j->rtindex != 0);
+ *qualscope = bms_add_member(*qualscope, j->rtindex);
+ root->outer_join_rels = bms_add_member(root->outer_join_rels,
+ j->rtindex);
*inner_join_rels = bms_union(left_inners, right_inners);
/* each side is both outer and inner */
nonnullable_rels = *qualscope;
@@ -976,32 +1006,44 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
my_quals = list_concat(my_quals, (List *) j->quals);
/*
- * For an OJ, form the SpecialJoinInfo now, because we need the OJ's
- * semantic scope (ojscope) to pass to distribute_qual_to_rels. But
- * we mustn't add it to join_info_list just yet, because we don't want
- * distribute_qual_to_rels to think it is an outer join below us.
- *
- * Semijoins are a bit of a hybrid: we build a SpecialJoinInfo, but we
- * want ojscope = NULL for distribute_qual_to_rels.
+ * For an OJ, form the SpecialJoinInfo now, because we need it for
+ * distribute_qual_to_rels. But we mustn't add it to join_info_list
+ * just yet, because we don't want distribute_qual_to_rels to think it
+ * is an outer join below us.
*/
if (j->jointype != JOIN_INNER)
- {
sjinfo = make_outerjoininfo(root,
leftids, rightids,
*inner_join_rels,
j->jointype,
+ j->rtindex,
my_quals);
- if (j->jointype == JOIN_SEMI)
- ojscope = NULL;
- else
- ojscope = bms_union(sjinfo->min_lefthand,
- sjinfo->min_righthand);
- }
else
- {
sjinfo = NULL;
+
+ /*
+ * If we have a LEFT JOIN whose ON qual is strict for any LHS
+ * relations, we may be able to commute the join with lower outer
+ * joins that null those relations. To do that, we must remove such
+ * lower outer joins from Var.varnullingrels fields within the qual,
+ * else subsequent processing will think that the qual has to be
+ * evaluated above such lower outer joins.
+ */
+ if (j->jointype == JOIN_LEFT && sjinfo->lhs_strict)
+ my_quals = remove_unneeded_nulling_relids(root, my_quals, sjinfo);
+
+ /*
+ * Now we can compute ojscope (we can't do it earlier, because
+ * remove_unneeded_nulling_relids might change the scope).
+ *
+ * Semijoins are a bit of a hybrid: we build a SpecialJoinInfo, but we
+ * want ojscope = NULL for distribute_qual_to_rels.
+ */
+ if (j->jointype == JOIN_INNER || j->jointype == JOIN_SEMI)
ojscope = NULL;
- }
+ else
+ ojscope = bms_union(sjinfo->min_lefthand,
+ sjinfo->min_righthand);
/* Process the JOIN's qual clauses */
foreach(l, my_quals)
@@ -1009,7 +1051,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
Node *qual = (Node *) lfirst(l);
distribute_qual_to_rels(root, qual,
- below_outer_join, j->jointype,
+ below_outer_join, sjinfo,
root->qual_security_level,
*qualscope,
ojscope, nonnullable_rels,
@@ -1112,7 +1154,7 @@ process_security_barrier_quals(PlannerInfo *root,
*/
distribute_qual_to_rels(root, qual,
below_outer_join,
- JOIN_INNER,
+ NULL,
security_level,
qualscope,
qualscope,
@@ -1135,6 +1177,7 @@ process_security_barrier_quals(PlannerInfo *root,
* right_rels: the base Relids syntactically on inner side of join
* inner_join_rels: base Relids participating in inner joins below this one
* jointype: what it says (must always be LEFT, FULL, SEMI, or ANTI)
+ * ojrelid: RT index of the join RTE (0 for SEMI, which isn't in the RT list)
* clause: the outer join's join condition (in implicit-AND format)
*
* The node should eventually be appended to root->join_info_list, but we
@@ -1148,7 +1191,8 @@ static SpecialJoinInfo *
make_outerjoininfo(PlannerInfo *root,
Relids left_rels, Relids right_rels,
Relids inner_join_rels,
- JoinType jointype, List *clause)
+ JoinType jointype, Index ojrelid,
+ List *clause)
{
SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo);
Relids clause_relids;
@@ -1196,6 +1240,7 @@ make_outerjoininfo(PlannerInfo *root,
sjinfo->syn_lefthand = left_rels;
sjinfo->syn_righthand = right_rels;
sjinfo->jointype = jointype;
+ sjinfo->ojrelid = ojrelid;
/* this always starts out false */
sjinfo->delay_upper_joins = false;
@@ -1206,6 +1251,7 @@ make_outerjoininfo(PlannerInfo *root,
{
sjinfo->min_lefthand = bms_copy(left_rels);
sjinfo->min_righthand = bms_copy(right_rels);
+ sjinfo->strict_relids = NULL; /* don't care about this */
sjinfo->lhs_strict = false; /* don't care about this */
return sjinfo;
}
@@ -1220,6 +1266,7 @@ make_outerjoininfo(PlannerInfo *root,
* rel's columns are all NULL?
*/
strict_relids = find_nonnullable_rels((Node *) clause);
+ sjinfo->strict_relids = strict_relids;
/* Remember whether the clause is strict for any LHS relations */
sjinfo->lhs_strict = bms_overlap(strict_relids, left_rels);
@@ -1258,6 +1305,9 @@ make_outerjoininfo(PlannerInfo *root,
otherinfo->syn_lefthand);
min_lefthand = bms_add_members(min_lefthand,
otherinfo->syn_righthand);
+ if (otherinfo->ojrelid != 0)
+ min_lefthand = bms_add_member(min_lefthand,
+ otherinfo->ojrelid);
}
if (bms_overlap(right_rels, otherinfo->syn_lefthand) ||
bms_overlap(right_rels, otherinfo->syn_righthand))
@@ -1266,6 +1316,9 @@ make_outerjoininfo(PlannerInfo *root,
otherinfo->syn_lefthand);
min_righthand = bms_add_members(min_righthand,
otherinfo->syn_righthand);
+ if (otherinfo->ojrelid != 0)
+ min_righthand = bms_add_member(min_righthand,
+ otherinfo->ojrelid);
}
/* Needn't do anything else with the full join */
continue;
@@ -1295,6 +1348,9 @@ make_outerjoininfo(PlannerInfo *root,
otherinfo->syn_lefthand);
min_lefthand = bms_add_members(min_lefthand,
otherinfo->syn_righthand);
+ if (otherinfo->ojrelid != 0)
+ min_lefthand = bms_add_member(min_lefthand,
+ otherinfo->ojrelid);
}
}
@@ -1337,6 +1393,9 @@ make_outerjoininfo(PlannerInfo *root,
otherinfo->syn_lefthand);
min_righthand = bms_add_members(min_righthand,
otherinfo->syn_righthand);
+ if (otherinfo->ojrelid != 0)
+ min_righthand = bms_add_member(min_righthand,
+ otherinfo->ojrelid);
}
}
}
@@ -1561,6 +1620,62 @@ compute_semijoin_info(PlannerInfo *root, SpecialJoinInfo *sjinfo, List *clause)
sjinfo->semi_rhs_exprs = semi_rhs_exprs;
}
+/*
+ * remove_unneeded_nulling_relids
+ * Remove lower outer joins from Vars (& PHVs) in the quals, if possible
+ *
+ * This paves the way to apply outer join identity 3 to commute the current
+ * LEFT JOIN with lower outer joins. We already know that the quals are
+ * strict for at least one LHS relation.
+ */
+static List *
+remove_unneeded_nulling_relids(PlannerInfo *root, List *quals,
+ SpecialJoinInfo *sjinfo)
+{
+ Relids old_nulling_relids;
+ Relids removable_relids;
+ ListCell *lc;
+
+ /*
+ * Find outer joins mentioned in nullingrel fields in the quals. If there
+ * aren't any (the common case), there's no need to work hard.
+ */
+ old_nulling_relids = get_nulling_relids((Node *) quals);
+ if (bms_is_empty(old_nulling_relids))
+ return quals;
+
+ /*
+ * Thumb through the existing SpecialJoinInfos (which describe all outer
+ * joins below this one, but not yet this one) to find the ones mentioned
+ * in the quals. If the current join's quals are strict for any rel of
+ * one's RHS, we can commute this join with that one, so remove it from
+ * the current join's min_lefthand and from the quals' nullingrel fields.
+ */
+ removable_relids = NULL;
+ foreach(lc, root->join_info_list)
+ {
+ SpecialJoinInfo *sjinfo2 = (SpecialJoinInfo *) lfirst(lc);
+
+ if (sjinfo2->jointype != JOIN_LEFT ||
+ !bms_is_member(sjinfo2->ojrelid, old_nulling_relids))
+ continue; /* it's not relevant */
+ if (bms_is_subset(sjinfo2->syn_righthand, sjinfo->syn_lefthand) &&
+ bms_overlap(sjinfo->strict_relids, sjinfo2->min_righthand))
+ {
+ sjinfo->min_lefthand = bms_del_member(sjinfo->min_lefthand,
+ sjinfo2->ojrelid);
+ removable_relids = bms_add_member(removable_relids,
+ sjinfo2->ojrelid);
+ }
+ }
+
+ if (removable_relids == NULL)
+ return quals; /* no hits, nothing to do */
+
+ return (List *) remove_nulling_relids((Node *) quals,
+ removable_relids, NULL);
+}
+
/*****************************************************************************
*
@@ -1582,7 +1697,7 @@ compute_semijoin_info(PlannerInfo *root, SpecialJoinInfo *sjinfo, List *clause)
* 'clause': the qual clause to be distributed
* 'below_outer_join': true if the qual is from a JOIN/ON that is below the
* nullable side of a higher-level outer join
- * 'jointype': type of join the qual is from (JOIN_INNER for a WHERE clause)
+ * 'sjinfo': join's SpecialJoinInfo (NULL for an inner join or WHERE clause)
* 'security_level': security_level to assign to the qual
* 'qualscope': set of baserels the qual's syntactic scope covers
* 'ojscope': NULL if not an outer-join qual, else the minimum set of baserels
@@ -1600,12 +1715,13 @@ compute_semijoin_info(PlannerInfo *root, SpecialJoinInfo *sjinfo, List *clause)
* level, which will be ojscope not necessarily qualscope.
*
* At the time this is called, root->join_info_list must contain entries for
- * all and only those special joins that are syntactically below this qual.
+ * all and only those special joins that are syntactically below this qual;
+ * in particular, the passed-in SpecialJoinInfo isn't yet in that list.
*/
static void
distribute_qual_to_rels(PlannerInfo *root, Node *clause,
bool below_outer_join,
- JoinType jointype,
+ SpecialJoinInfo *sjinfo,
Index security_level,
Relids qualscope,
Relids ojscope,
@@ -1642,7 +1758,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
PostponedQual *pq = (PostponedQual *) palloc(sizeof(PostponedQual));
Assert(root->hasLateralRTEs); /* shouldn't happen otherwise */
- Assert(jointype == JOIN_INNER); /* mustn't postpone past outer join */
+ Assert(sjinfo == NULL); /* mustn't postpone past outer join */
pq->qual = clause;
pq->relids = relids;
*postponed_qual_list = lappend(*postponed_qual_list, pq);
@@ -1704,7 +1820,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
{
relids =
get_relids_in_jointree((Node *) root->parse->jointree,
- false);
+ true, false);
qualscope = bms_copy(relids);
}
}
@@ -1946,11 +2062,15 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
restrictinfo);
return;
}
- if (jointype == JOIN_FULL)
+ if (sjinfo && sjinfo->jointype == JOIN_FULL)
{
/* FULL JOIN (above tests cannot match in this case) */
+ FullJoinClauseInfo *fjinfo = palloc(sizeof(FullJoinClauseInfo));
+
+ fjinfo->rinfo = restrictinfo;
+ fjinfo->sjinfo = sjinfo;
root->full_join_clauses = lappend(root->full_join_clauses,
- restrictinfo);
+ fjinfo);
return;
}
/* nope, so fall through to distribute_restrictinfo_to_rels */
@@ -2344,7 +2464,7 @@ process_implied_equality(PlannerInfo *root,
{
relids =
get_relids_in_jointree((Node *) root->parse->jointree,
- false);
+ true, false);
}
}
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index a0f2390334..593c3c9fcc 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -898,7 +898,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
*/
if (rte->lateral && root->hasJoinRTEs)
rte->subquery = (Query *)
- flatten_join_alias_vars(root->parse,
+ flatten_join_alias_vars(root, root->parse,
(Node *) rte->subquery);
}
else if (rte->rtekind == RTE_FUNCTION)
@@ -1099,7 +1099,7 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind)
kind == EXPRKIND_VALUES ||
kind == EXPRKIND_TABLESAMPLE ||
kind == EXPRKIND_TABLEFUNC))
- expr = flatten_join_alias_vars(root->parse, expr);
+ expr = flatten_join_alias_vars(root, root->parse, expr);
/*
* Simplify constant expressions. For function RTEs, this was already
@@ -1791,8 +1791,8 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
withCheckOptions = (List *)
adjust_appendrel_attrs_multilevel(root,
(Node *) withCheckOptions,
- this_result_rel->relids,
- top_result_rel->relids);
+ this_result_rel,
+ top_result_rel);
withCheckOptionLists = lappend(withCheckOptionLists,
withCheckOptions);
}
@@ -1804,8 +1804,8 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
returningList = (List *)
adjust_appendrel_attrs_multilevel(root,
(Node *) returningList,
- this_result_rel->relids,
- top_result_rel->relids);
+ this_result_rel,
+ top_result_rel);
returningLists = lappend(returningLists,
returningList);
}
@@ -1826,13 +1826,13 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
leaf_action->qual =
adjust_appendrel_attrs_multilevel(root,
(Node *) action->qual,
- this_result_rel->relids,
- top_result_rel->relids);
+ this_result_rel,
+ top_result_rel);
leaf_action->targetList = (List *)
adjust_appendrel_attrs_multilevel(root,
(Node *) action->targetList,
- this_result_rel->relids,
- top_result_rel->relids);
+ this_result_rel,
+ top_result_rel);
if (leaf_action->commandType == CMD_UPDATE)
leaf_action->updateColnos =
adjust_inherited_attnums_multilevel(root,
@@ -2221,7 +2221,7 @@ preprocess_rowmarks(PlannerInfo *root)
* make a bitmapset of all base rels and then remove the items we don't
* need or have FOR [KEY] UPDATE/SHARE marks for.
*/
- rels = get_relids_in_jointree((Node *) parse->jointree, false);
+ rels = get_relids_in_jointree((Node *) parse->jointree, false, false);
if (parse->resultRelation)
rels = bms_del_member(rels, parse->resultRelation);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 9cef92cab2..3b720ec5d6 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -151,6 +151,9 @@ static Var *search_indexed_tlist_for_var(Var *var,
indexed_tlist *itlist,
int newvarno,
int rtoffset);
+static Var *search_indexed_tlist_for_phv(PlaceHolderVar *phv,
+ indexed_tlist *itlist,
+ int newvarno);
static Var *search_indexed_tlist_for_non_var(Expr *node,
indexed_tlist *itlist,
int newvarno);
@@ -1530,6 +1533,7 @@ set_foreignscan_references(PlannerInfo *root,
}
fscan->fs_relids = offset_relid_set(fscan->fs_relids, rtoffset);
+ fscan->fs_base_relids = offset_relid_set(fscan->fs_base_relids, rtoffset);
/* Adjust resultRelation if it's valid */
if (fscan->resultRelation > 0)
@@ -2108,6 +2112,7 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context)
/* At scan level, we should always just evaluate the contained expr */
PlaceHolderVar *phv = (PlaceHolderVar *) node;
+ Assert(phv->phnullingrels == NULL);
return fix_scan_expr_mutator((Node *) phv->phexpr, context);
}
if (IsA(node, AlternativeSubPlan))
@@ -2228,33 +2233,12 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset)
/*
* Now we need to fix up the targetlist and qpqual, which are logically
* above the join. This means they should not re-use any input expression
- * that was computed in the nullable side of an outer join. Vars and
- * PlaceHolderVars are fine, so we can implement this restriction just by
- * clearing has_non_vars in the indexed_tlist structs.
+ * that was computed in the nullable side of an outer join.
*
- * XXX This is a grotty workaround for the fact that we don't clearly
- * distinguish between a Var appearing below an outer join and the "same"
- * Var appearing above it. If we did, we'd not need to hack the matching
- * rules this way.
+ * XXX we will probably need to pass some flag down to indicate that this
+ * context applies, so that search_indexed_tlist_for_var() and siblings
+ * can correctly check for varnullingrels matches.
*/
- switch (join->jointype)
- {
- case JOIN_LEFT:
- case JOIN_SEMI:
- case JOIN_ANTI:
- inner_itlist->has_non_vars = false;
- break;
- case JOIN_RIGHT:
- outer_itlist->has_non_vars = false;
- break;
- case JOIN_FULL:
- outer_itlist->has_non_vars = false;
- inner_itlist->has_non_vars = false;
- break;
- default:
- break;
- }
-
join->plan.targetlist = fix_join_expr(root,
join->plan.targetlist,
outer_itlist,
@@ -2543,7 +2527,7 @@ set_dummy_tlist_references(Plan *plan, int rtoffset)
* tlist_member() searches.
*
* The result of this function is an indexed_tlist struct to pass to
- * search_indexed_tlist_for_var() or search_indexed_tlist_for_non_var().
+ * search_indexed_tlist_for_var() and siblings.
* When done, the indexed_tlist may be freed with a single pfree().
*/
static indexed_tlist *
@@ -2665,6 +2649,8 @@ search_indexed_tlist_for_var(Var *var, indexed_tlist *itlist,
/* Found a match */
Var *newvar = copyVar(var);
+ /* XXX we oughta check varnullingrels match here ... */
+
newvar->varno = newvarno;
newvar->varattno = vinfo->resno;
if (newvar->varnosyn > 0)
@@ -2677,15 +2663,55 @@ search_indexed_tlist_for_var(Var *var, indexed_tlist *itlist,
}
/*
- * search_indexed_tlist_for_non_var --- find a non-Var in an indexed tlist
+ * search_indexed_tlist_for_phv --- find a PlaceHolderVar in an indexed tlist
*
* If a match is found, return a Var constructed to reference the tlist item.
* If no match, return NULL.
*
- * NOTE: it is a waste of time to call this unless itlist->has_ph_vars or
- * itlist->has_non_vars. Furthermore, set_join_references() relies on being
- * able to prevent matching of non-Vars by clearing itlist->has_non_vars,
- * so there's a correctness reason not to call it unless that's set.
+ * NOTE: it is a waste of time to call this unless itlist->has_ph_vars.
+ */
+static Var *
+search_indexed_tlist_for_phv(PlaceHolderVar *phv,
+ indexed_tlist *itlist, int newvarno)
+{
+ ListCell *lc;
+
+ foreach(lc, itlist->tlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+ if (tle->expr && IsA(tle->expr, PlaceHolderVar))
+ {
+ PlaceHolderVar *subphv = (PlaceHolderVar *) tle->expr;
+ Var *newvar;
+
+ /*
+ * Analogously to search_indexed_tlist_for_var, we match on phid
+ * only. We don't use equal(), partially for speed but mostly
+ * because phnullingrels might not be exactly equal.
+ *
+ * XXX we really oughta verify phnullingrels.
+ */
+ if (phv->phid != subphv->phid)
+ continue;
+
+ /* Found a matching subplan output expression */
+ newvar = makeVarFromTargetEntry(newvarno, tle);
+ newvar->varnosyn = 0; /* wasn't ever a plain Var */
+ newvar->varattnosyn = 0;
+ return newvar;
+ }
+ }
+ return NULL; /* no match */
+}
+
+/*
+ * search_indexed_tlist_for_non_var --- find a non-Var/PHV in an indexed tlist
+ *
+ * If a match is found, return a Var constructed to reference the tlist item.
+ * If no match, return NULL.
+ *
+ * NOTE: it is a waste of time to call this unless itlist->has_non_vars.
*/
static Var *
search_indexed_tlist_for_non_var(Expr *node,
@@ -2870,22 +2896,23 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
/* See if the PlaceHolderVar has bubbled up from a lower plan node */
if (context->outer_itlist && context->outer_itlist->has_ph_vars)
{
- newvar = search_indexed_tlist_for_non_var((Expr *) phv,
- context->outer_itlist,
- OUTER_VAR);
+ newvar = search_indexed_tlist_for_phv(phv,
+ context->outer_itlist,
+ OUTER_VAR);
if (newvar)
return (Node *) newvar;
}
if (context->inner_itlist && context->inner_itlist->has_ph_vars)
{
- newvar = search_indexed_tlist_for_non_var((Expr *) phv,
- context->inner_itlist,
- INNER_VAR);
+ newvar = search_indexed_tlist_for_phv(phv,
+ context->inner_itlist,
+ INNER_VAR);
if (newvar)
return (Node *) newvar;
}
/* If not supplied by input plans, evaluate the contained expr */
+ /* XXX assert something about phnullingrels */
return fix_join_expr_mutator((Node *) phv->phexpr, context);
}
/* Try matching more complex expressions too, if tlists have any */
@@ -2994,13 +3021,14 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context)
/* See if the PlaceHolderVar has bubbled up from a lower plan node */
if (context->subplan_itlist->has_ph_vars)
{
- newvar = search_indexed_tlist_for_non_var((Expr *) phv,
- context->subplan_itlist,
- context->newvarno);
+ newvar = search_indexed_tlist_for_phv(phv,
+ context->subplan_itlist,
+ context->newvarno);
if (newvar)
return (Node *) newvar;
}
/* If not supplied by input plan, evaluate the contained expr */
+ /* XXX assert something about phnullingrels */
return fix_upper_expr_mutator((Node *) phv->phexpr, context);
}
/* Try matching more complex expressions too, if tlist has any */
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 0bd99acf83..7b22254173 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -49,17 +49,28 @@ typedef struct pullup_replace_vars_context
* pullup (set only if target_rte->lateral) */
bool *outer_hasSubLinks; /* -> outer query's hasSubLinks */
int varno; /* varno of subquery */
- bool need_phvs; /* do we need PlaceHolderVars? */
- bool wrap_non_vars; /* do we need 'em on *all* non-Vars? */
+ bool wrap_non_vars; /* do we need all non-Var outputs to be PHVs? */
Node **rv_cache; /* cache for results with PHVs */
} pullup_replace_vars_context;
-typedef struct reduce_outer_joins_state
+typedef struct reduce_outer_joins_pass1_state
{
Relids relids; /* base relids within this subtree */
bool contains_outer; /* does subtree contain outer join(s)? */
List *sub_states; /* List of states for subtree components */
-} reduce_outer_joins_state;
+} reduce_outer_joins_pass1_state;
+
+typedef struct reduce_outer_joins_pass2_state
+{
+ Relids inner_reduced; /* OJ relids reduced to plain inner joins */
+ List *partial_reduced; /* List of partially reduced FULL joins */
+} reduce_outer_joins_pass2_state;
+
+typedef struct reduce_outer_joins_partial_state
+{
+ int full_join_rti; /* RT index of a formerly-FULL join */
+ Relids unreduced_side; /* relids in its still-nullable side */
+} reduce_outer_joins_partial_state;
static Node *pull_up_sublinks_jointree_recurse(PlannerInfo *root, Node *jtnode,
Relids *relids);
@@ -68,12 +79,10 @@ static Node *pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node,
Node **jtlink2, Relids available_rels2);
static Node *pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
JoinExpr *lowest_outer_join,
- JoinExpr *lowest_nulling_outer_join,
AppendRelInfo *containing_appendrel);
static Node *pull_up_simple_subquery(PlannerInfo *root, Node *jtnode,
RangeTblEntry *rte,
JoinExpr *lowest_outer_join,
- JoinExpr *lowest_nulling_outer_join,
AppendRelInfo *containing_appendrel);
static Node *pull_up_simple_union_all(PlannerInfo *root, Node *jtnode,
RangeTblEntry *rte);
@@ -90,7 +99,6 @@ static Node *pull_up_simple_values(PlannerInfo *root, Node *jtnode,
static bool is_simple_values(PlannerInfo *root, RangeTblEntry *rte);
static Node *pull_up_constant_function(PlannerInfo *root, Node *jtnode,
RangeTblEntry *rte,
- JoinExpr *lowest_nulling_outer_join,
AppendRelInfo *containing_appendrel);
static bool is_simple_union_all(Query *subquery);
static bool is_simple_union_all_recurse(Node *setOp, Query *setOpQuery,
@@ -101,25 +109,27 @@ static bool jointree_contains_lateral_outer_refs(PlannerInfo *root,
Relids safe_upper_varnos);
static void perform_pullup_replace_vars(PlannerInfo *root,
pullup_replace_vars_context *rvcontext,
- JoinExpr *lowest_nulling_outer_join,
AppendRelInfo *containing_appendrel);
static void replace_vars_in_jointree(Node *jtnode,
- pullup_replace_vars_context *context,
- JoinExpr *lowest_nulling_outer_join);
+ pullup_replace_vars_context *context);
static Node *pullup_replace_vars(Node *expr,
pullup_replace_vars_context *context);
static Node *pullup_replace_vars_callback(Var *var,
replace_rte_variables_context *context);
static Query *pullup_replace_vars_subquery(Query *query,
pullup_replace_vars_context *context);
-static reduce_outer_joins_state *reduce_outer_joins_pass1(Node *jtnode);
+static reduce_outer_joins_pass1_state *reduce_outer_joins_pass1(Node *jtnode);
static void reduce_outer_joins_pass2(Node *jtnode,
- reduce_outer_joins_state *state,
+ reduce_outer_joins_pass1_state *state1,
+ reduce_outer_joins_pass2_state *state2,
PlannerInfo *root,
Relids nonnullable_rels,
List *nonnullable_vars,
List *forced_null_vars);
-static Node *remove_useless_results_recurse(PlannerInfo *root, Node *jtnode);
+static void report_reduced_full_join(reduce_outer_joins_pass2_state *state2,
+ int rtindex, Relids relids);
+static Node *remove_useless_results_recurse(PlannerInfo *root, Node *jtnode,
+ Relids *dropped_outer_joins);
static int get_result_relid(PlannerInfo *root, Node *jtnode);
static void remove_result_refs(PlannerInfo *root, int varno, Node *newjtloc);
static bool find_dependent_phvs(PlannerInfo *root, int varno);
@@ -764,7 +774,7 @@ pull_up_subqueries(PlannerInfo *root)
/* Recursion starts with no containing join nor appendrel */
root->parse->jointree = (FromExpr *)
pull_up_subqueries_recurse(root, (Node *) root->parse->jointree,
- NULL, NULL, NULL);
+ NULL, NULL);
/* We should still have a FromExpr */
Assert(IsA(root->parse->jointree, FromExpr));
}
@@ -779,12 +789,6 @@ pull_up_subqueries(PlannerInfo *root)
* lowest_outer_join references the lowest such JoinExpr node; otherwise
* it is NULL. We use this to constrain the effects of LATERAL subqueries.
*
- * If this jointree node is within the nullable side of an outer join, then
- * lowest_nulling_outer_join references the lowest such JoinExpr node;
- * otherwise it is NULL. This forces use of the PlaceHolderVar mechanism for
- * references to non-nullable targetlist items, but only for references above
- * that join.
- *
* If we are looking at a member subquery of an append relation,
* containing_appendrel describes that relation; else it is NULL.
* This forces use of the PlaceHolderVar mechanism for all non-Var targetlist
@@ -801,15 +805,14 @@ pull_up_subqueries(PlannerInfo *root)
* Notice also that we can't turn pullup_replace_vars loose on the whole
* jointree, because it'd return a mutated copy of the tree; we have to
* invoke it just on the quals, instead. This behavior is what makes it
- * reasonable to pass lowest_outer_join and lowest_nulling_outer_join as
- * pointers rather than some more-indirect way of identifying the lowest
- * OJs. Likewise, we don't replace append_rel_list members but only their
- * substructure, so the containing_appendrel reference is safe to use.
+ * reasonable to pass lowest_outer_join as a pointer rather than some
+ * more-indirect way of identifying the lowest OJ. Likewise, we don't
+ * replace append_rel_list members but only their substructure, so the
+ * containing_appendrel reference is safe to use.
*/
static Node *
pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
JoinExpr *lowest_outer_join,
- JoinExpr *lowest_nulling_outer_join,
AppendRelInfo *containing_appendrel)
{
Assert(jtnode != NULL);
@@ -831,7 +834,6 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
is_safe_append_member(rte->subquery)))
return pull_up_simple_subquery(root, jtnode, rte,
lowest_outer_join,
- lowest_nulling_outer_join,
containing_appendrel);
/*
@@ -864,7 +866,6 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
*/
if (rte->rtekind == RTE_FUNCTION)
return pull_up_constant_function(root, jtnode, rte,
- lowest_nulling_outer_join,
containing_appendrel);
/* Otherwise, do nothing at this node. */
@@ -880,7 +881,6 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
{
lfirst(l) = pull_up_subqueries_recurse(root, lfirst(l),
lowest_outer_join,
- lowest_nulling_outer_join,
NULL);
}
}
@@ -895,11 +895,9 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
case JOIN_INNER:
j->larg = pull_up_subqueries_recurse(root, j->larg,
lowest_outer_join,
- lowest_nulling_outer_join,
NULL);
j->rarg = pull_up_subqueries_recurse(root, j->rarg,
lowest_outer_join,
- lowest_nulling_outer_join,
NULL);
break;
case JOIN_LEFT:
@@ -907,31 +905,25 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
case JOIN_ANTI:
j->larg = pull_up_subqueries_recurse(root, j->larg,
j,
- lowest_nulling_outer_join,
NULL);
j->rarg = pull_up_subqueries_recurse(root, j->rarg,
- j,
j,
NULL);
break;
case JOIN_FULL:
j->larg = pull_up_subqueries_recurse(root, j->larg,
- j,
j,
NULL);
j->rarg = pull_up_subqueries_recurse(root, j->rarg,
- j,
j,
NULL);
break;
case JOIN_RIGHT:
j->larg = pull_up_subqueries_recurse(root, j->larg,
- j,
j,
NULL);
j->rarg = pull_up_subqueries_recurse(root, j->rarg,
j,
- lowest_nulling_outer_join,
NULL);
break;
default:
@@ -961,7 +953,6 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
static Node *
pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
JoinExpr *lowest_outer_join,
- JoinExpr *lowest_nulling_outer_join,
AppendRelInfo *containing_appendrel)
{
Query *parse = root->parse;
@@ -1085,7 +1076,8 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
* maybe even in the rewriter; but for now let's just fix this case here.)
*/
subquery->targetList = (List *)
- flatten_join_alias_vars(subroot->parse, (Node *) subquery->targetList);
+ flatten_join_alias_vars(subroot, subroot->parse,
+ (Node *) subquery->targetList);
/*
* Adjust level-0 varnos in subquery so that we can append its rangetable
@@ -1107,31 +1099,25 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
* The subquery's targetlist items are now in the appropriate form to
* insert into the top query, except that we may need to wrap them in
* PlaceHolderVars. Set up required context data for pullup_replace_vars.
+ * (Note that we should include the subquery's inner joins in relids,
+ * since it may include join alias vars referencing them.)
*/
rvcontext.root = root;
rvcontext.targetlist = subquery->targetList;
rvcontext.target_rte = rte;
if (rte->lateral)
rvcontext.relids = get_relids_in_jointree((Node *) subquery->jointree,
- true);
+ true, true);
else /* won't need relids */
rvcontext.relids = NULL;
rvcontext.outer_hasSubLinks = &parse->hasSubLinks;
rvcontext.varno = varno;
- /* these flags will be set below, if needed */
- rvcontext.need_phvs = false;
+ /* this flag will be set below, if needed */
rvcontext.wrap_non_vars = false;
/* initialize cache array with indexes 0 .. length(tlist) */
rvcontext.rv_cache = palloc0((list_length(subquery->targetList) + 1) *
sizeof(Node *));
- /*
- * If we are under an outer join then non-nullable items and lateral
- * references may have to be turned into PlaceHolderVars.
- */
- if (lowest_nulling_outer_join != NULL)
- rvcontext.need_phvs = true;
-
/*
* If we are dealing with an appendrel member then anything that's not a
* simple Var has to be turned into a PlaceHolderVar. We force this to
@@ -1140,10 +1126,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
* expression actually available from the appendrel.
*/
if (containing_appendrel != NULL)
- {
- rvcontext.need_phvs = true;
rvcontext.wrap_non_vars = true;
- }
/*
* If the parent query uses grouping sets, we need a PlaceHolderVar for
@@ -1155,10 +1138,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
* that pullup_replace_vars hasn't currently got.)
*/
if (parse->groupingSets)
- {
- rvcontext.need_phvs = true;
rvcontext.wrap_non_vars = true;
- }
/*
* Replace all of the top query's references to the subquery's outputs
@@ -1166,7 +1146,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
* replace any of the jointree structure.
*/
perform_pullup_replace_vars(root, &rvcontext,
- lowest_nulling_outer_join,
containing_appendrel);
/*
@@ -1233,7 +1212,8 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
{
Relids subrelids;
- subrelids = get_relids_in_jointree((Node *) subquery->jointree, false);
+ subrelids = get_relids_in_jointree((Node *) subquery->jointree,
+ true, false);
substitute_phv_relids((Node *) parse, varno, subrelids);
fix_append_rel_relids(root->append_rel_list, varno, subrelids);
}
@@ -1424,7 +1404,7 @@ pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root, int parentRTindex,
rtr = makeNode(RangeTblRef);
rtr->rtindex = childRTindex;
(void) pull_up_subqueries_recurse(root, (Node *) rtr,
- NULL, NULL, appinfo);
+ NULL, appinfo);
}
else if (IsA(setOp, SetOperationStmt))
{
@@ -1561,7 +1541,7 @@ is_simple_subquery(PlannerInfo *root, Query *subquery, RangeTblEntry *rte,
{
restricted = true;
safe_upper_varnos = get_relids_in_jointree((Node *) lowest_outer_join,
- true);
+ true, true);
}
else
{
@@ -1673,7 +1653,6 @@ pull_up_simple_values(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
rvcontext.relids = NULL;
rvcontext.outer_hasSubLinks = &parse->hasSubLinks;
rvcontext.varno = varno;
- rvcontext.need_phvs = false;
rvcontext.wrap_non_vars = false;
/* initialize cache array with indexes 0 .. length(tlist) */
rvcontext.rv_cache = palloc0((list_length(tlist) + 1) *
@@ -1685,7 +1664,7 @@ pull_up_simple_values(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
* any of the jointree structure. We can assume there's no outer joins or
* appendrels in the dummy Query that surrounds a VALUES RTE.
*/
- perform_pullup_replace_vars(root, &rvcontext, NULL, NULL);
+ perform_pullup_replace_vars(root, &rvcontext, NULL);
/*
* There should be no appendrels to fix, nor any outer joins and hence no
@@ -1784,7 +1763,6 @@ is_simple_values(PlannerInfo *root, RangeTblEntry *rte)
static Node *
pull_up_constant_function(PlannerInfo *root, Node *jtnode,
RangeTblEntry *rte,
- JoinExpr *lowest_nulling_outer_join,
AppendRelInfo *containing_appendrel)
{
Query *parse = root->parse;
@@ -1836,40 +1814,26 @@ pull_up_constant_function(PlannerInfo *root, Node *jtnode,
rvcontext.outer_hasSubLinks = &parse->hasSubLinks;
rvcontext.varno = ((RangeTblRef *) jtnode)->rtindex;
- /* these flags will be set below, if needed */
- rvcontext.need_phvs = false;
+ /* this flag will be set below, if needed */
rvcontext.wrap_non_vars = false;
/* initialize cache array with indexes 0 .. length(tlist) */
rvcontext.rv_cache = palloc0((list_length(rvcontext.targetlist) + 1) *
sizeof(Node *));
- /*
- * If we are under an outer join then non-nullable items and lateral
- * references may have to be turned into PlaceHolderVars.
- */
- if (lowest_nulling_outer_join != NULL)
- rvcontext.need_phvs = true;
-
/*
* If we are dealing with an appendrel member then anything that's not a
* simple Var has to be turned into a PlaceHolderVar. (See comments in
* pull_up_simple_subquery().)
*/
if (containing_appendrel != NULL)
- {
- rvcontext.need_phvs = true;
rvcontext.wrap_non_vars = true;
- }
/*
* If the parent query uses grouping sets, we need a PlaceHolderVar for
* anything that's not a simple Var.
*/
if (parse->groupingSets)
- {
- rvcontext.need_phvs = true;
rvcontext.wrap_non_vars = true;
- }
/*
* Replace all of the top query's references to the RTE's output with
@@ -1877,7 +1841,6 @@ pull_up_constant_function(PlannerInfo *root, Node *jtnode,
* jointree structure.
*/
perform_pullup_replace_vars(root, &rvcontext,
- lowest_nulling_outer_join,
containing_appendrel);
/*
@@ -2099,13 +2062,11 @@ jointree_contains_lateral_outer_refs(PlannerInfo *root, Node *jtnode,
*
* Caller has already filled *rvcontext with data describing what to
* substitute for Vars referencing the target subquery. In addition
- * we need the identity of the lowest outer join that can null the
- * target subquery, and its containing appendrel if any.
+ * we need the identity of the containing appendrel if any.
*/
static void
perform_pullup_replace_vars(PlannerInfo *root,
pullup_replace_vars_context *rvcontext,
- JoinExpr *lowest_nulling_outer_join,
AppendRelInfo *containing_appendrel)
{
Query *parse = root->parse;
@@ -2149,38 +2110,31 @@ perform_pullup_replace_vars(PlannerInfo *root,
pullup_replace_vars((Node *) action->targetList, rvcontext);
}
}
- replace_vars_in_jointree((Node *) parse->jointree, rvcontext,
- lowest_nulling_outer_join);
+ replace_vars_in_jointree((Node *) parse->jointree, rvcontext);
Assert(parse->setOperations == NULL);
parse->havingQual = pullup_replace_vars(parse->havingQual, rvcontext);
/*
* Replace references in the translated_vars lists of appendrels. When
- * pulling up an appendrel member, we do not need PHVs in the list of the
- * parent appendrel --- there isn't any outer join between. Elsewhere,
- * use PHVs for safety. (This analysis could be made tighter but it seems
- * unlikely to be worth much trouble.)
+ * pulling up an appendrel member, we do not want to force PHVs in the
+ * list of the parent appendrel --- there isn't any outer join between.
+ * Elsewhere, use PHVs for safety. (This analysis could be made tighter
+ * but it seems unlikely to be worth much trouble.)
*/
foreach(lc, root->append_rel_list)
{
AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc);
- bool save_need_phvs = rvcontext->need_phvs;
+ bool save_wrap_non_vars = rvcontext->wrap_non_vars;
if (appinfo == containing_appendrel)
- rvcontext->need_phvs = false;
+ rvcontext->wrap_non_vars = false;
appinfo->translated_vars = (List *)
pullup_replace_vars((Node *) appinfo->translated_vars, rvcontext);
- rvcontext->need_phvs = save_need_phvs;
+ rvcontext->wrap_non_vars = save_wrap_non_vars;
}
/*
* Replace references in the joinaliasvars lists of join RTEs.
- *
- * You might think that we could avoid using PHVs for alias vars of joins
- * below lowest_nulling_outer_join, but that doesn't work because the
- * alias vars could be referenced above that join; we need the PHVs to be
- * present in such references after the alias vars get flattened. (It
- * might be worth trying to be smarter here, someday.)
*/
foreach(lc, parse->rtable)
{
@@ -2197,14 +2151,10 @@ perform_pullup_replace_vars(PlannerInfo *root,
* Helper routine for perform_pullup_replace_vars: do pullup_replace_vars on
* every expression in the jointree, without changing the jointree structure
* itself. Ugly, but there's no other way...
- *
- * If we are at or below lowest_nulling_outer_join, we can suppress use of
- * PlaceHolderVars wrapped around the replacement expressions.
*/
static void
replace_vars_in_jointree(Node *jtnode,
- pullup_replace_vars_context *context,
- JoinExpr *lowest_nulling_outer_join)
+ pullup_replace_vars_context *context)
{
if (jtnode == NULL)
return;
@@ -2217,7 +2167,7 @@ replace_vars_in_jointree(Node *jtnode,
* jointree scan, rather than a scan of the rtable, for a couple of
* reasons: we can avoid processing no-longer-referenced RTEs, and we
* can use the appropriate setting of need_phvs depending on whether
- * the RTE is above possibly-nulling outer joins or not.
+ * the RTE is above possibly-nulling outer joins or not. XXX fix
*/
int varno = ((RangeTblRef *) jtnode)->rtindex;
@@ -2274,42 +2224,30 @@ replace_vars_in_jointree(Node *jtnode,
ListCell *l;
foreach(l, f->fromlist)
- replace_vars_in_jointree(lfirst(l), context,
- lowest_nulling_outer_join);
+ replace_vars_in_jointree(lfirst(l), context);
f->quals = pullup_replace_vars(f->quals, context);
}
else if (IsA(jtnode, JoinExpr))
{
JoinExpr *j = (JoinExpr *) jtnode;
- bool save_need_phvs = context->need_phvs;
+ bool save_wrap_non_vars = context->wrap_non_vars;
- if (j == lowest_nulling_outer_join)
- {
- /* no more PHVs in or below this join */
- context->need_phvs = false;
- lowest_nulling_outer_join = NULL;
- }
- replace_vars_in_jointree(j->larg, context, lowest_nulling_outer_join);
- replace_vars_in_jointree(j->rarg, context, lowest_nulling_outer_join);
+ replace_vars_in_jointree(j->larg, context);
+ replace_vars_in_jointree(j->rarg, context);
/*
- * Use PHVs within the join quals of a full join, even when it's the
- * lowest nulling outer join. Otherwise, we cannot identify which
- * side of the join a pulled-up var-free expression came from, which
- * can lead to failure to make a plan at all because none of the quals
- * appear to be mergeable or hashable conditions. For this purpose we
- * don't care about the state of wrap_non_vars, so leave it alone.
+ * Use PHVs within the join quals of a full join. Otherwise, we
+ * cannot identify which side of the join a pulled-up var-free
+ * expression came from, which can lead to failure to make a plan at
+ * all because none of the quals appear to be mergeable or hashable
+ * conditions.
*/
if (j->jointype == JOIN_FULL)
- context->need_phvs = true;
+ context->wrap_non_vars = true;
j->quals = pullup_replace_vars(j->quals, context);
- /*
- * We don't bother to update the colvars list, since it won't be used
- * again ...
- */
- context->need_phvs = save_need_phvs;
+ context->wrap_non_vars = save_wrap_non_vars;
}
else
elog(ERROR, "unrecognized node type: %d",
@@ -2338,8 +2276,18 @@ pullup_replace_vars_callback(Var *var,
{
pullup_replace_vars_context *rcon = (pullup_replace_vars_context *) context->callback_arg;
int varattno = var->varattno;
+ bool need_phv;
Node *newnode;
+ /*
+ * We need a PlaceHolderVar if the Var-to-be-replaced has nonempty
+ * varnullingrels (unless we find below that the replacement expression is
+ * a Var or PlaceHolderVar that we can just add the nullingrels to). We
+ * also need one if the caller has instructed us that all non-Var/PHV
+ * replacements need to be wrapped for identification purposes.
+ */
+ need_phv = (var->varnullingrels != NULL) || rcon->wrap_non_vars;
+
/*
* If PlaceHolderVars are needed, we cache the modified expressions in
* rcon->rv_cache[]. This is not in hopes of any material speed gain
@@ -2348,13 +2296,16 @@ pullup_replace_vars_callback(Var *var,
* and possibly prevent optimizations that rely on recognizing different
* references to the same subquery output as being equal(). So it's worth
* a bit of extra effort to avoid it.
+ *
+ * The cached items have phlevelsup = 0 and phnullingrels = NULL; we'll
+ * copy them and adjust those values for this reference site below.
*/
- if (rcon->need_phvs &&
+ if (need_phv &&
varattno >= InvalidAttrNumber &&
varattno <= list_length(rcon->targetlist) &&
rcon->rv_cache[varattno] != NULL)
{
- /* Just copy the entry and fall through to adjust its varlevelsup */
+ /* Just copy the entry and fall through to adjust phlevelsup etc */
newnode = copyObject(rcon->rv_cache[varattno]);
}
else if (varattno == InvalidAttrNumber)
@@ -2363,7 +2314,7 @@ pullup_replace_vars_callback(Var *var,
RowExpr *rowexpr;
List *colnames;
List *fields;
- bool save_need_phvs = rcon->need_phvs;
+ bool save_wrap_non_vars = rcon->wrap_non_vars;
int save_sublevelsup = context->sublevels_up;
/*
@@ -2374,18 +2325,18 @@ pullup_replace_vars_callback(Var *var,
* the RowExpr for use of the executor and ruleutils.c.
*
* In order to be able to cache the results, we always generate the
- * expansion with varlevelsup = 0, and then adjust if needed.
+ * expansion with varlevelsup = 0, and then adjust below if needed.
*/
expandRTE(rcon->target_rte,
var->varno, 0 /* not varlevelsup */ , var->location,
(var->vartype != RECORDOID),
&colnames, &fields);
- /* Adjust the generated per-field Vars, but don't insert PHVs */
- rcon->need_phvs = false;
+ /* Expand the generated per-field Vars, but don't insert PHVs there */
+ rcon->wrap_non_vars = false;
context->sublevels_up = 0; /* to match the expandRTE output */
fields = (List *) replace_rte_variables_mutator((Node *) fields,
context);
- rcon->need_phvs = save_need_phvs;
+ rcon->wrap_non_vars = save_wrap_non_vars;
context->sublevels_up = save_sublevelsup;
rowexpr = makeNode(RowExpr);
@@ -2403,14 +2354,13 @@ pullup_replace_vars_callback(Var *var,
* expression to yield NULL, not ROW(NULL,NULL,...) when it is forced
* to null by an outer join.
*/
- if (rcon->need_phvs)
+ if (need_phv)
{
- /* RowExpr is certainly not strict, so always need PHV */
newnode = (Node *)
make_placeholder_expr(rcon->root,
(Expr *) newnode,
bms_make_singleton(rcon->varno));
- /* cache it with the PHV, and with varlevelsup still zero */
+ /* cache it with the PHV, and with phlevelsup etc not set yet */
rcon->rv_cache[InvalidAttrNumber] = copyObject(newnode);
}
}
@@ -2427,7 +2377,7 @@ pullup_replace_vars_callback(Var *var,
newnode = (Node *) copyObject(tle->expr);
/* Insert PlaceHolderVar if needed */
- if (rcon->need_phvs)
+ if (need_phv)
{
bool wrap;
@@ -2453,69 +2403,61 @@ pullup_replace_vars_callback(Var *var,
/* No need to wrap a PlaceHolderVar with another one, either */
wrap = false;
}
- else if (rcon->wrap_non_vars)
- {
- /* Wrap all non-Vars in a PlaceHolderVar */
- wrap = true;
- }
else
{
/*
- * If it contains a Var of the subquery being pulled up, and
- * does not contain any non-strict constructs, then it's
- * certainly nullable so we don't need to insert a
- * PlaceHolderVar.
- *
- * This analysis could be tighter: in particular, a non-strict
- * construct hidden within a lower-level PlaceHolderVar is not
- * reason to add another PHV. But for now it doesn't seem
- * worth the code to be more exact.
- *
- * Note: in future maybe we should insert a PlaceHolderVar
- * anyway, if the tlist item is expensive to evaluate?
- *
- * For a LATERAL subquery, we have to check the actual var
- * membership of the node, but if it's non-lateral then any
- * level-zero var must belong to the subquery.
+ * Must wrap, either because we need a place to insert
+ * varnullingrels or because caller told us to wrap
+ * everything.
*/
- if ((rcon->target_rte->lateral ?
- bms_overlap(pull_varnos(rcon->root, (Node *) newnode),
- rcon->relids) :
- contain_vars_of_level((Node *) newnode, 0)) &&
- !contain_nonstrict_functions((Node *) newnode))
- {
- /* No wrap needed */
- wrap = false;
- }
- else
- {
- /* Else wrap it in a PlaceHolderVar */
- wrap = true;
- }
+ wrap = true;
}
if (wrap)
+ {
newnode = (Node *)
make_placeholder_expr(rcon->root,
(Expr *) newnode,
bms_make_singleton(rcon->varno));
- /*
- * Cache it if possible (ie, if the attno is in range, which it
- * probably always should be). We can cache the value even if we
- * decided we didn't need a PHV, since this result will be
- * suitable for any request that has need_phvs.
- */
- if (varattno > InvalidAttrNumber &&
- varattno <= list_length(rcon->targetlist))
- rcon->rv_cache[varattno] = copyObject(newnode);
+ /*
+ * Cache it if possible (ie, if the attno is in range, which
+ * it probably always should be).
+ */
+ if (varattno > InvalidAttrNumber &&
+ varattno <= list_length(rcon->targetlist))
+ rcon->rv_cache[varattno] = copyObject(newnode);
+ }
}
}
- /* Must adjust varlevelsup if tlist item is from higher query */
+ /* Must adjust varlevelsup if replaced Var is within a subquery */
if (var->varlevelsup > 0)
IncrementVarSublevelsUp(newnode, var->varlevelsup, 0);
+ /* Propagate any varnullingrels into the replacement Var or PHV */
+ if (var->varnullingrels != NULL)
+ {
+ if (IsA(newnode, Var))
+ {
+ Var *newvar = (Var *) newnode;
+
+ Assert(newvar->varlevelsup == var->varlevelsup);
+ newvar->varnullingrels = bms_add_members(newvar->varnullingrels,
+ var->varnullingrels);
+ }
+ else if (IsA(newnode, PlaceHolderVar))
+ {
+ PlaceHolderVar *newphv = (PlaceHolderVar *) newnode;
+
+ Assert(newphv->phlevelsup == var->varlevelsup);
+ newphv->phnullingrels = bms_add_members(newphv->phnullingrels,
+ var->varnullingrels);
+ }
+ else
+ elog(ERROR, "failed to wrap a non-Var");
+ }
+
return newnode;
}
@@ -2674,7 +2616,9 @@ flatten_simple_union_all(PlannerInfo *root)
void
reduce_outer_joins(PlannerInfo *root)
{
- reduce_outer_joins_state *state;
+ reduce_outer_joins_pass1_state *state1;
+ reduce_outer_joins_pass2_state state2;
+ ListCell *lc;
/*
* To avoid doing strictness checks on more quals than necessary, we want
@@ -2685,14 +2629,44 @@ reduce_outer_joins(PlannerInfo *root)
* join(s) below each side of each join clause. The second pass examines
* qual clauses and changes join types as it descends the tree.
*/
- state = reduce_outer_joins_pass1((Node *) root->parse->jointree);
+ state1 = reduce_outer_joins_pass1((Node *) root->parse->jointree);
/* planner.c shouldn't have called me if no outer joins */
- if (state == NULL || !state->contains_outer)
+ if (state1 == NULL || !state1->contains_outer)
elog(ERROR, "so where are the outer joins?");
+ state2.inner_reduced = NULL;
+ state2.partial_reduced = NIL;
+
reduce_outer_joins_pass2((Node *) root->parse->jointree,
- state, root, NULL, NIL, NIL);
+ state1, &state2,
+ root, NULL, NIL, NIL);
+
+ /*
+ * If we successfully reduced the strength of any outer joins, we must
+ * remove references to those joins as nulling rels. This is handled as
+ * an additional pass, for simplicity and because we can handle all
+ * fully-reduced joins in a single pass over the parse tree.
+ */
+ if (!bms_is_empty(state2.inner_reduced))
+ root->parse = (Query *)
+ remove_nulling_relids((Node *) root->parse,
+ state2.inner_reduced,
+ NULL);
+
+ /*
+ * Partially-reduced full joins have to be done one at a time, since
+ * they'll each need a different setting of except_relids.
+ */
+ foreach(lc, state2.partial_reduced)
+ {
+ reduce_outer_joins_partial_state *statep = lfirst(lc);
+
+ root->parse = (Query *)
+ remove_nulling_relids((Node *) root->parse,
+ bms_make_singleton(statep->full_join_rti),
+ statep->unreduced_side);
+ }
}
/*
@@ -2700,13 +2674,13 @@ reduce_outer_joins(PlannerInfo *root)
*
* Returns a state node describing the given jointree node.
*/
-static reduce_outer_joins_state *
+static reduce_outer_joins_pass1_state *
reduce_outer_joins_pass1(Node *jtnode)
{
- reduce_outer_joins_state *result;
+ reduce_outer_joins_pass1_state *result;
- result = (reduce_outer_joins_state *)
- palloc(sizeof(reduce_outer_joins_state));
+ result = (reduce_outer_joins_pass1_state *)
+ palloc(sizeof(reduce_outer_joins_pass1_state));
result->relids = NULL;
result->contains_outer = false;
result->sub_states = NIL;
@@ -2726,7 +2700,7 @@ reduce_outer_joins_pass1(Node *jtnode)
foreach(l, f->fromlist)
{
- reduce_outer_joins_state *sub_state;
+ reduce_outer_joins_pass1_state *sub_state;
sub_state = reduce_outer_joins_pass1(lfirst(l));
result->relids = bms_add_members(result->relids,
@@ -2738,7 +2712,7 @@ reduce_outer_joins_pass1(Node *jtnode)
else if (IsA(jtnode, JoinExpr))
{
JoinExpr *j = (JoinExpr *) jtnode;
- reduce_outer_joins_state *sub_state;
+ reduce_outer_joins_pass1_state *sub_state;
/* join's own RT index is not wanted in result->relids */
if (IS_OUTER_JOIN(j->jointype))
@@ -2766,15 +2740,23 @@ reduce_outer_joins_pass1(Node *jtnode)
* reduce_outer_joins_pass2 - phase 2 processing
*
* jtnode: current jointree node
- * state: state data collected by phase 1 for this node
+ * state1: state data collected by phase 1 for this node
+ * state2: where to accumulate info about successfully-reduced joins
* root: toplevel planner state
* nonnullable_rels: set of base relids forced non-null by upper quals
* nonnullable_vars: list of Vars forced non-null by upper quals
* forced_null_vars: list of Vars forced null by upper quals
+ *
+ * Returns info in state2 about outer joins that were successfully simplified.
+ * Joins that were fully reduced to inner joins are all added to
+ * state2->inner_reduced. If a full join is reduced to a left join,
+ * it needs its own entry in state2->partial_reduced, since that will
+ * require custom processing to remove only the correct nullingrel markers.
*/
static void
reduce_outer_joins_pass2(Node *jtnode,
- reduce_outer_joins_state *state,
+ reduce_outer_joins_pass1_state *state1,
+ reduce_outer_joins_pass2_state *state2,
PlannerInfo *root,
Relids nonnullable_rels,
List *nonnullable_vars,
@@ -2808,13 +2790,14 @@ reduce_outer_joins_pass2(Node *jtnode,
pass_forced_null_vars = list_concat(pass_forced_null_vars,
forced_null_vars);
/* And recurse --- but only into interesting subtrees */
- Assert(list_length(f->fromlist) == list_length(state->sub_states));
- forboth(l, f->fromlist, s, state->sub_states)
+ Assert(list_length(f->fromlist) == list_length(state1->sub_states));
+ forboth(l, f->fromlist, s, state1->sub_states)
{
- reduce_outer_joins_state *sub_state = lfirst(s);
+ reduce_outer_joins_pass1_state *sub_state = lfirst(s);
if (sub_state->contains_outer)
- reduce_outer_joins_pass2(lfirst(l), sub_state, root,
+ reduce_outer_joins_pass2(lfirst(l), sub_state,
+ state2, root,
pass_nonnullable_rels,
pass_nonnullable_vars,
pass_forced_null_vars);
@@ -2827,8 +2810,8 @@ reduce_outer_joins_pass2(Node *jtnode,
JoinExpr *j = (JoinExpr *) jtnode;
int rtindex = j->rtindex;
JoinType jointype = j->jointype;
- reduce_outer_joins_state *left_state = linitial(state->sub_states);
- reduce_outer_joins_state *right_state = lsecond(state->sub_states);
+ reduce_outer_joins_pass1_state *left_state = linitial(state1->sub_states);
+ reduce_outer_joins_pass1_state *right_state = lsecond(state1->sub_states);
List *local_nonnullable_vars = NIL;
bool computed_local_nonnullable_vars = false;
@@ -2851,12 +2834,22 @@ reduce_outer_joins_pass2(Node *jtnode,
if (bms_overlap(nonnullable_rels, right_state->relids))
jointype = JOIN_INNER;
else
+ {
jointype = JOIN_LEFT;
+ /* Also report partial reduction in state2 */
+ report_reduced_full_join(state2, rtindex,
+ right_state->relids);
+ }
}
else
{
if (bms_overlap(nonnullable_rels, right_state->relids))
+ {
jointype = JOIN_RIGHT;
+ /* Also report partial reduction in state2 */
+ report_reduced_full_join(state2, rtindex,
+ left_state->relids);
+ }
}
break;
case JOIN_SEMI:
@@ -2889,8 +2882,8 @@ reduce_outer_joins_pass2(Node *jtnode,
j->larg = j->rarg;
j->rarg = tmparg;
jointype = JOIN_LEFT;
- right_state = linitial(state->sub_states);
- left_state = lsecond(state->sub_states);
+ right_state = linitial(state1->sub_states);
+ left_state = lsecond(state1->sub_states);
}
/*
@@ -2923,7 +2916,10 @@ reduce_outer_joins_pass2(Node *jtnode,
jointype = JOIN_ANTI;
}
- /* Apply the jointype change, if any, to both jointree node and RTE */
+ /*
+ * Apply the jointype change, if any, to both jointree node and RTE.
+ * Also, if we changed an RTE to INNER, add its RTI to inner_reduced.
+ */
if (rtindex && jointype != j->jointype)
{
RangeTblEntry *rte = rt_fetch(rtindex, root->parse->rtable);
@@ -2931,6 +2927,9 @@ reduce_outer_joins_pass2(Node *jtnode,
Assert(rte->rtekind == RTE_JOIN);
Assert(rte->jointype == j->jointype);
rte->jointype = jointype;
+ if (jointype == JOIN_INNER)
+ state2->inner_reduced = bms_add_member(state2->inner_reduced,
+ rtindex);
}
j->jointype = jointype;
@@ -3011,7 +3010,8 @@ reduce_outer_joins_pass2(Node *jtnode,
pass_nonnullable_vars = NIL;
pass_forced_null_vars = NIL;
}
- reduce_outer_joins_pass2(j->larg, left_state, root,
+ reduce_outer_joins_pass2(j->larg, left_state,
+ state2, root,
pass_nonnullable_rels,
pass_nonnullable_vars,
pass_forced_null_vars);
@@ -3033,7 +3033,8 @@ reduce_outer_joins_pass2(Node *jtnode,
pass_nonnullable_vars = NIL;
pass_forced_null_vars = NIL;
}
- reduce_outer_joins_pass2(j->rarg, right_state, root,
+ reduce_outer_joins_pass2(j->rarg, right_state,
+ state2, root,
pass_nonnullable_rels,
pass_nonnullable_vars,
pass_forced_null_vars);
@@ -3046,6 +3047,19 @@ reduce_outer_joins_pass2(Node *jtnode,
(int) nodeTag(jtnode));
}
+/* Helper for reduce_outer_joins_pass2 */
+static void
+report_reduced_full_join(reduce_outer_joins_pass2_state *state2,
+ int rtindex, Relids relids)
+{
+ reduce_outer_joins_partial_state *statep;
+
+ statep = palloc(sizeof(reduce_outer_joins_partial_state));
+ statep->full_join_rti = rtindex;
+ statep->unreduced_side = relids;
+ state2->partial_reduced = lappend(state2->partial_reduced, statep);
+}
+
/*
* remove_useless_result_rtes
@@ -3087,16 +3101,34 @@ reduce_outer_joins_pass2(Node *jtnode,
void
remove_useless_result_rtes(PlannerInfo *root)
{
+ Relids dropped_outer_joins = NULL;
ListCell *cell;
/* Top level of jointree must always be a FromExpr */
Assert(IsA(root->parse->jointree, FromExpr));
/* Recurse ... */
root->parse->jointree = (FromExpr *)
- remove_useless_results_recurse(root, (Node *) root->parse->jointree);
+ remove_useless_results_recurse(root,
+ (Node *) root->parse->jointree,
+ &dropped_outer_joins);
/* We should still have a FromExpr */
Assert(IsA(root->parse->jointree, FromExpr));
+ /*
+ * If we removed any outer-join nodes from the jointree, run around and
+ * remove references to those joins as nulling rels. (There could be such
+ * references in PHVs that we pulled up out of the original subquery that
+ * the RESULT rel replaced. This is kosher on the grounds that we now
+ * know that such an outer join wouldn't really have nulled anything.) We
+ * don't do this during the main recursion, for simplicity and because we
+ * can handle all such joins in a single pass over the parse tree.
+ */
+ if (!bms_is_empty(dropped_outer_joins))
+ root->parse = (Query *)
+ remove_nulling_relids((Node *) root->parse,
+ dropped_outer_joins,
+ NULL);
+
/*
* Remove any PlanRowMark referencing an RTE_RESULT RTE. We obviously
* must do that for any RTE_RESULT that we just removed. But one for a
@@ -3122,9 +3154,12 @@ remove_useless_result_rtes(PlannerInfo *root)
* Recursive guts of remove_useless_result_rtes.
*
* This recursively processes the jointree and returns a modified jointree.
+ * In addition, the RT indexes of any removed outer-join nodes are added to
+ * *dropped_outer_joins.
*/
static Node *
-remove_useless_results_recurse(PlannerInfo *root, Node *jtnode)
+remove_useless_results_recurse(PlannerInfo *root, Node *jtnode,
+ Relids *dropped_outer_joins)
{
Assert(jtnode != NULL);
if (IsA(jtnode, RangeTblRef))
@@ -3152,7 +3187,8 @@ remove_useless_results_recurse(PlannerInfo *root, Node *jtnode)
int varno;
/* Recursively transform child ... */
- child = remove_useless_results_recurse(root, child);
+ child = remove_useless_results_recurse(root, child,
+ dropped_outer_joins);
/* ... and stick it back into the tree */
lfirst(cell) = child;
@@ -3201,8 +3237,10 @@ remove_useless_results_recurse(PlannerInfo *root, Node *jtnode)
int varno;
/* First, recurse */
- j->larg = remove_useless_results_recurse(root, j->larg);
- j->rarg = remove_useless_results_recurse(root, j->rarg);
+ j->larg = remove_useless_results_recurse(root, j->larg,
+ dropped_outer_joins);
+ j->rarg = remove_useless_results_recurse(root, j->rarg,
+ dropped_outer_joins);
/* Apply join-type-specific optimization rules */
switch (j->jointype)
@@ -3270,6 +3308,8 @@ remove_useless_results_recurse(PlannerInfo *root, Node *jtnode)
!find_dependent_phvs(root, varno)))
{
remove_result_refs(root, varno, j->larg);
+ *dropped_outer_joins = bms_add_member(*dropped_outer_joins,
+ j->rtindex);
jtnode = j->larg;
}
break;
@@ -3280,6 +3320,8 @@ remove_useless_results_recurse(PlannerInfo *root, Node *jtnode)
!find_dependent_phvs(root, varno)))
{
remove_result_refs(root, varno, j->rarg);
+ *dropped_outer_joins = bms_add_member(*dropped_outer_joins,
+ j->rtindex);
jtnode = j->rarg;
}
break;
@@ -3294,11 +3336,14 @@ remove_useless_results_recurse(PlannerInfo *root, Node *jtnode)
* Unlike the LEFT/RIGHT cases, we just Assert that there are
* no PHVs that need to be evaluated at the semijoin's RHS,
* since the rest of the query couldn't reference any outputs
- * of the semijoin's RHS.
+ * of the semijoin's RHS. Also, we don't need to worry about
+ * removing traces of the join's rtindex, since it hasn't got
+ * one.
*/
if ((varno = get_result_relid(root, j->rarg)) != 0)
{
Assert(!find_dependent_phvs(root, varno));
+ Assert(j->rtindex == 0);
remove_result_refs(root, varno, j->larg);
if (j->quals)
jtnode = (Node *)
@@ -3367,7 +3412,7 @@ remove_result_refs(PlannerInfo *root, int varno, Node *newjtloc)
{
Relids subrelids;
- subrelids = get_relids_in_jointree(newjtloc, false);
+ subrelids = get_relids_in_jointree(newjtloc, true, false);
Assert(!bms_is_empty(subrelids));
substitute_phv_relids((Node *) root->parse, varno, subrelids);
fix_append_rel_relids(root->append_rel_list, varno, subrelids);
@@ -3479,7 +3524,7 @@ find_dependent_phvs_in_jointree(PlannerInfo *root, Node *node, int varno)
* are not marked LATERAL, though, since they couldn't possibly contain
* any cross-references to other RTEs.
*/
- subrelids = get_relids_in_jointree(node, false);
+ subrelids = get_relids_in_jointree(node, false, false);
relid = -1;
while ((relid = bms_next_member(subrelids, relid)) >= 0)
{
@@ -3623,11 +3668,17 @@ fix_append_rel_relids(List *append_rel_list, int varno, Relids subrelids)
/*
* get_relids_in_jointree: get set of RT indexes present in a jointree
*
- * If include_joins is true, join RT indexes are included; if false,
- * only base rels are included.
+ * Base-relation relids are always included in the result.
+ * If include_outer_joins is true, outer-join RT indexes are included.
+ * If include_inner_joins is true, inner-join RT indexes are included.
+ *
+ * Note that for most purposes in the planner, outer joins are included
+ * in standard relid sets. Setting include_inner_joins true is only
+ * appropriate for special purposes during subquery flattening.
*/
Relids
-get_relids_in_jointree(Node *jtnode, bool include_joins)
+get_relids_in_jointree(Node *jtnode, bool include_outer_joins,
+ bool include_inner_joins)
{
Relids result = NULL;
@@ -3648,18 +3699,34 @@ get_relids_in_jointree(Node *jtnode, bool include_joins)
{
result = bms_join(result,
get_relids_in_jointree(lfirst(l),
- include_joins));
+ include_outer_joins,
+ include_inner_joins));
}
}
else if (IsA(jtnode, JoinExpr))
{
JoinExpr *j = (JoinExpr *) jtnode;
- result = get_relids_in_jointree(j->larg, include_joins);
+ result = get_relids_in_jointree(j->larg,
+ include_outer_joins,
+ include_inner_joins);
result = bms_join(result,
- get_relids_in_jointree(j->rarg, include_joins));
- if (include_joins && j->rtindex)
- result = bms_add_member(result, j->rtindex);
+ get_relids_in_jointree(j->rarg,
+ include_outer_joins,
+ include_inner_joins));
+ if (j->rtindex)
+ {
+ if (j->jointype == JOIN_INNER)
+ {
+ if (include_inner_joins)
+ result = bms_add_member(result, j->rtindex);
+ }
+ else
+ {
+ if (include_outer_joins)
+ result = bms_add_member(result, j->rtindex);
+ }
+ }
}
else
elog(ERROR, "unrecognized node type: %d",
@@ -3668,7 +3735,7 @@ get_relids_in_jointree(Node *jtnode, bool include_joins)
}
/*
- * get_relids_for_join: get set of base RT indexes making up a join
+ * get_relids_for_join: get set of base+OJ RT indexes making up a join
*/
Relids
get_relids_for_join(Query *query, int joinrelid)
@@ -3679,7 +3746,7 @@ get_relids_for_join(Query *query, int joinrelid)
joinrelid);
if (!jtnode)
elog(ERROR, "could not find join node %d", joinrelid);
- return get_relids_in_jointree(jtnode, false);
+ return get_relids_in_jointree(jtnode, true, false);
}
/*
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 9d4bb47027..4d8da9f6e2 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -228,6 +228,12 @@ adjust_appendrel_attrs_mutator(Node *node,
if (var->varlevelsup != 0)
return (Node *) var; /* no changes needed */
+ /*
+ * You might think we need to adjust var->varnullingrels, but that
+ * shouldn't need any changes. It will contain outer-join relids,
+ * while the transformation we are making affects only baserels.
+ */
+
for (cnt = 0; cnt < nappinfos; cnt++)
{
if (var->varno == appinfos[cnt]->parent_relid)
@@ -348,6 +354,8 @@ adjust_appendrel_attrs_mutator(Node *node,
var = copyObject(ridinfo->rowidvar);
/* ... but use the correct relid */
var->varno = leaf_relid;
+ /* identity vars shouldn't have nulling rels */
+ Assert(var->varnullingrels == NULL);
/* varnosyn in the RowIdentityVarInfo is probably wrong */
var->varnosyn = 0;
var->varattnosyn = 0;
@@ -392,8 +400,11 @@ adjust_appendrel_attrs_mutator(Node *node,
(void *) context);
/* now fix PlaceHolderVar's relid sets */
if (phv->phlevelsup == 0)
- phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos,
- context->appinfos);
+ {
+ phv->phrels = adjust_child_relids(phv->phrels,
+ nappinfos, appinfos);
+ /* as above, we needn't touch phnullingrels */
+ }
return (Node *) phv;
}
/* Shouldn't need to handle planner auxiliary nodes here */
@@ -486,32 +497,26 @@ adjust_appendrel_attrs_mutator(Node *node,
*/
Node *
adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
- Relids child_relids,
- Relids top_parent_relids)
+ RelOptInfo *childrel,
+ RelOptInfo *parentrel)
{
AppendRelInfo **appinfos;
- Bitmapset *parent_relids = NULL;
int nappinfos;
- int cnt;
-
- Assert(bms_num_members(child_relids) == bms_num_members(top_parent_relids));
-
- appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos);
- /* Construct relids set for the immediate parent of given child. */
- for (cnt = 0; cnt < nappinfos; cnt++)
+ /* Recurse if immediate parent is not the top parent. */
+ if (childrel->parent != parentrel)
{
- AppendRelInfo *appinfo = appinfos[cnt];
-
- parent_relids = bms_add_member(parent_relids, appinfo->parent_relid);
+ if (childrel->parent)
+ node = adjust_appendrel_attrs_multilevel(root, node,
+ childrel->parent,
+ parentrel);
+ else
+ elog(ERROR, "presented child rel is not a child of parent rel");
}
- /* Recurse if immediate parent is not the top parent. */
- if (!bms_equal(parent_relids, top_parent_relids))
- node = adjust_appendrel_attrs_multilevel(root, node, parent_relids,
- top_parent_relids);
+ /* Now translate for this child. */
+ appinfos = find_appinfos_by_relids(root, childrel->relids, &nappinfos);
- /* Now translate for this child */
node = adjust_appendrel_attrs(root, node, nappinfos, appinfos);
pfree(appinfos);
@@ -554,56 +559,43 @@ adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos)
}
/*
- * 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.
+ * Substitute child relids for parent relids in a Relid set.
+ * The childrel can be multiple inheritance levels below the parent.
*/
Relids
adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
- Relids child_relids, Relids top_parent_relids)
+ RelOptInfo *childrel,
+ RelOptInfo *parentrel)
{
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 the given relids set doesn't contain any of the parent relids, it
+ * will remain unchanged.
*/
- if (!bms_overlap(relids, top_parent_relids))
+ if (!bms_overlap(relids, parentrel->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))
+ if (childrel->parent != parentrel)
{
- tmp_result = adjust_child_relids_multilevel(root, relids,
- parent_relids,
- top_parent_relids);
- relids = tmp_result;
+ if (childrel->parent)
+ relids = adjust_child_relids_multilevel(root, relids,
+ childrel->parent,
+ parentrel);
+ else
+ elog(ERROR, "presented child rel is not a child of parent rel");
}
- result = adjust_child_relids(relids, nappinfos, appinfos);
+ /* Now translate for this child. */
+ appinfos = find_appinfos_by_relids(root, childrel->relids, &nappinfos);
+
+ relids = 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;
+ return relids;
}
/*
@@ -694,8 +686,8 @@ get_translated_update_targetlist(PlannerInfo *root, Index relid,
*processed_tlist = (List *)
adjust_appendrel_attrs_multilevel(root,
(Node *) root->processed_tlist,
- bms_make_singleton(relid),
- bms_make_singleton(root->parse->resultRelation));
+ find_base_rel(root, relid),
+ find_base_rel(root, root->parse->resultRelation));
if (update_colnos)
*update_colnos =
adjust_inherited_attnums_multilevel(root, root->update_colnos,
@@ -706,7 +698,11 @@ get_translated_update_targetlist(PlannerInfo *root, Index relid,
/*
* find_appinfos_by_relids
- * Find AppendRelInfo structures for all relations specified by relids.
+ * Find AppendRelInfo structures for base relations listed in relids.
+ *
+ * The relids argument is typically a join relation's relids, which can
+ * include outer-join RT indexes in addition to baserels. We silently
+ * ignore the outer joins.
*
* The AppendRelInfos are returned in an array, which can be pfree'd by the
* caller. *nappinfos is set to the number of entries in the array.
@@ -718,8 +714,9 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
int cnt = 0;
int i;
- *nappinfos = bms_num_members(relids);
- appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * *nappinfos);
+ /* Allocate an array that's certainly big enough */
+ appinfos = (AppendRelInfo **)
+ palloc(sizeof(AppendRelInfo *) * bms_num_members(relids));
i = -1;
while ((i = bms_next_member(relids, i)) >= 0)
@@ -727,10 +724,17 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
AppendRelInfo *appinfo = root->append_rel_array[i];
if (!appinfo)
+ {
+ /* Probably i is an OJ index, but let's check */
+ if (find_base_rel_ignore_join(root, i) == NULL)
+ continue;
+ /* It's a base rel, but we lack an append_rel_array entry */
elog(ERROR, "child rel %d not found in append_rel_array", i);
+ }
appinfos[cnt++] = appinfo;
}
+ *nappinfos = cnt;
return appinfos;
}
@@ -772,6 +776,7 @@ add_row_identity_var(PlannerInfo *root, Var *orig_var,
Assert(IsA(orig_var, Var));
Assert(orig_var->varno == rtindex);
Assert(orig_var->varlevelsup == 0);
+ Assert(orig_var->varnullingrels == NULL);
/*
* If we're doing non-inherited UPDATE/DELETE/MERGE, there's little need
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 533df86ff7..c82fd451b2 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2022,14 +2022,16 @@ is_pseudo_constant_clause_relids(Node *clause, Relids relids)
* NumRelids
* (formerly clause_relids)
*
- * Returns the number of different relations referenced in 'clause'.
+ * Returns the number of different base relations referenced in 'clause'.
*/
int
NumRelids(PlannerInfo *root, Node *clause)
{
+ int result;
Relids varnos = pull_varnos(root, clause);
- int result = bms_num_members(varnos);
+ varnos = bms_del_members(varnos, root->outer_join_rels);
+ result = bms_num_members(varnos);
bms_free(varnos);
return result;
}
diff --git a/src/backend/optimizer/util/joininfo.c b/src/backend/optimizer/util/joininfo.c
index d4cffdb198..afd243f5d8 100644
--- a/src/backend/optimizer/util/joininfo.c
+++ b/src/backend/optimizer/util/joininfo.c
@@ -88,8 +88,8 @@ have_relevant_joinclause(PlannerInfo *root,
* not depend on context).
*
* 'restrictinfo' describes the join clause
- * 'join_relids' is the list of relations participating in the join clause
- * (there must be more than one)
+ * 'join_relids' is the set of relations participating in the join clause
+ * (some of these could be outer joins)
*/
void
add_join_clause_to_rels(PlannerInfo *root,
@@ -101,8 +101,11 @@ add_join_clause_to_rels(PlannerInfo *root,
cur_relid = -1;
while ((cur_relid = bms_next_member(join_relids, cur_relid)) >= 0)
{
- RelOptInfo *rel = find_base_rel(root, cur_relid);
+ RelOptInfo *rel = find_base_rel_ignore_join(root, cur_relid);
+ /* We only need to add the clause to baserels */
+ if (rel == NULL)
+ continue;
rel->joininfo = lappend(rel->joininfo, restrictinfo);
}
}
@@ -115,8 +118,8 @@ add_join_clause_to_rels(PlannerInfo *root,
* discover that a relation need not be joined at all.
*
* 'restrictinfo' describes the join clause
- * 'join_relids' is the list of relations participating in the join clause
- * (there must be more than one)
+ * 'join_relids' is the set of relations participating in the join clause
+ * (some of these could be outer joins)
*/
void
remove_join_clause_from_rels(PlannerInfo *root,
@@ -128,7 +131,11 @@ remove_join_clause_from_rels(PlannerInfo *root,
cur_relid = -1;
while ((cur_relid = bms_next_member(join_relids, cur_relid)) >= 0)
{
- RelOptInfo *rel = find_base_rel(root, cur_relid);
+ RelOptInfo *rel = find_base_rel_ignore_join(root, cur_relid);
+
+ /* We would only have added the clause to baserels */
+ if (rel == NULL)
+ continue;
/*
* Remove the restrictinfo from the list. Pointer comparison is
diff --git a/src/backend/optimizer/util/orclauses.c b/src/backend/optimizer/util/orclauses.c
index b1363df065..a62d4587ea 100644
--- a/src/backend/optimizer/util/orclauses.c
+++ b/src/backend/optimizer/util/orclauses.c
@@ -338,7 +338,9 @@ consider_new_or_clause(PlannerInfo *root, RelOptInfo *rel,
sjinfo.syn_lefthand = sjinfo.min_lefthand;
sjinfo.syn_righthand = sjinfo.min_righthand;
sjinfo.jointype = JOIN_INNER;
+ sjinfo.ojrelid = 0;
/* we don't bother trying to make the remaining fields valid */
+ sjinfo.strict_relids = NULL;
sjinfo.lhs_strict = false;
sjinfo.delay_upper_joins = false;
sjinfo.semi_can_btree = false;
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index e2a3c110ce..93235ee3e2 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1307,7 +1307,7 @@ create_append_path(PlannerInfo *root,
* Apply query-wide LIMIT if known and path is for sole base relation.
* (Handling this at this low level is a bit klugy.)
*/
- if (root != NULL && bms_equal(rel->relids, root->all_baserels))
+ if (root != NULL && bms_equal(rel->relids, root->all_query_rels))
pathnode->limit_tuples = root->limit_tuples;
else
pathnode->limit_tuples = -1.0;
@@ -1427,7 +1427,7 @@ create_merge_append_path(PlannerInfo *root,
* Apply query-wide LIMIT if known and path is for sole base relation.
* (Handling this at this low level is a bit klugy.)
*/
- if (bms_equal(rel->relids, root->all_baserels))
+ if (bms_equal(rel->relids, root->all_query_rels))
pathnode->limit_tuples = root->limit_tuples;
else
pathnode->limit_tuples = -1.0;
@@ -3996,8 +3996,8 @@ reparameterize_path_by_child(PlannerInfo *root, Path *path,
#define ADJUST_CHILD_ATTRS(node) \
((node) = \
(List *) adjust_appendrel_attrs_multilevel(root, (Node *) (node), \
- child_rel->relids, \
- child_rel->top_parent_relids))
+ child_rel, \
+ child_rel->top_parent))
#define REPARAMETERIZE_CHILD_PATH(path) \
do { \
@@ -4027,7 +4027,7 @@ do { \
* doesn't need reparameterization.
*/
if (!path->param_info ||
- !bms_overlap(PATH_REQ_OUTER(path), child_rel->top_parent_relids))
+ !bms_overlap(PATH_REQ_OUTER(path), child_rel->top_parent->relids))
return path;
/*
@@ -4214,8 +4214,8 @@ do { \
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);
+ child_rel,
+ child_rel->top_parent);
/* If we already have a PPI for this parameterization, just return it */
new_ppi = find_param_path_info(new_path->parent, required_outer);
@@ -4251,7 +4251,7 @@ do { \
* outer relation is laterally referenced in this relation.
*/
if (bms_overlap(path->parent->lateral_relids,
- child_rel->top_parent_relids))
+ child_rel->top_parent->relids))
{
new_path->pathtarget = copy_pathtarget(new_path->pathtarget);
ADJUST_CHILD_ATTRS(new_path->pathtarget->exprs);
diff --git a/src/backend/optimizer/util/placeholder.c b/src/backend/optimizer/util/placeholder.c
index 3b0f0584f0..8226e6c0f7 100644
--- a/src/backend/optimizer/util/placeholder.c
+++ b/src/backend/optimizer/util/placeholder.c
@@ -32,8 +32,14 @@ static void find_placeholders_in_expr(PlannerInfo *root, Node *expr);
* make_placeholder_expr
* Make a PlaceHolderVar for the given expression.
*
- * phrels is the syntactic location (as a set of baserels) to attribute
+ * phrels is the syntactic location (as a set of relids) to attribute
* to the expression.
+ *
+ * The caller is responsible for adjusting phlevelsup and phnullingrels
+ * as needed. Because we do not know here which query level the PHV
+ * will be associated with, it's important that this function touches
+ * only root->glob; messing with other parts of PlannerInfo would be
+ * likely to do the wrong thing.
*/
PlaceHolderVar *
make_placeholder_expr(PlannerInfo *root, Expr *expr, Relids phrels)
@@ -42,8 +48,9 @@ make_placeholder_expr(PlannerInfo *root, Expr *expr, Relids phrels)
phv->phexpr = expr;
phv->phrels = phrels;
+ phv->phnullingrels = NULL; /* caller may change this later */
phv->phid = ++(root->glob->lastPHId);
- phv->phlevelsup = 0;
+ phv->phlevelsup = 0; /* caller may change this later */
return phv;
}
@@ -317,6 +324,8 @@ update_placeholder_eval_levels(PlannerInfo *root, SpecialJoinInfo *new_sjinfo)
sjinfo->min_lefthand);
eval_at = bms_add_members(eval_at,
sjinfo->min_righthand);
+ if (sjinfo->ojrelid)
+ eval_at = bms_add_member(eval_at, sjinfo->ojrelid);
/* we'll need another iteration */
found_some = true;
}
@@ -390,9 +399,16 @@ add_placeholders_to_base_rels(PlannerInfo *root)
bms_nonempty_difference(phinfo->ph_needed, eval_at))
{
RelOptInfo *rel = find_base_rel(root, varno);
+ PlaceHolderVar *phv;
- rel->reltarget->exprs = lappend(rel->reltarget->exprs,
- copyObject(phinfo->ph_var));
+ /*
+ * As in add_vars_to_targetlist(), a value computed at scan level
+ * has not yet been nulled by any outer join, so set its
+ * phnullingrels to empty.
+ */
+ phv = copyObject(phinfo->ph_var);
+ phv->phnullingrels = NULL;
+ rel->reltarget->exprs = lappend(rel->reltarget->exprs, phv);
/* reltarget's cost and width fields will be updated later */
}
}
@@ -411,7 +427,8 @@ add_placeholders_to_base_rels(PlannerInfo *root)
*/
void
add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
- RelOptInfo *outer_rel, RelOptInfo *inner_rel)
+ RelOptInfo *outer_rel, RelOptInfo *inner_rel,
+ SpecialJoinInfo *sjinfo)
{
Relids relids = joinrel->relids;
ListCell *lc;
@@ -426,10 +443,11 @@ add_placeholders_to_joinrel(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 */
- joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs,
- phinfo->ph_var);
- joinrel->reltarget->width += phinfo->ph_width;
+ /*
+ * Yup, we must add it to the output. Make a copy so we can
+ * adjust phnullingrels if needed.
+ */
+ PlaceHolderVar *phv = copyObject(phinfo->ph_var);
/*
* Charge the cost of evaluating the contained expression if
@@ -442,16 +460,42 @@ add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
* with that; but we might want to improve it later by
* refiguring the reltarget costs for each pair of inputs.
*/
- if (!bms_is_subset(phinfo->ph_eval_at, outer_rel->relids) &&
- !bms_is_subset(phinfo->ph_eval_at, inner_rel->relids))
+ if (bms_is_subset(phinfo->ph_eval_at, outer_rel->relids))
+ {
+ if (sjinfo->jointype == JOIN_FULL && sjinfo->ojrelid != 0)
+ {
+ /* PHV's value can be nulled at this join */
+ phv->phnullingrels = bms_add_member(phv->phnullingrels,
+ sjinfo->ojrelid);
+ }
+ }
+ else if (bms_is_subset(phinfo->ph_eval_at, inner_rel->relids))
{
+ if (sjinfo->jointype != JOIN_INNER && sjinfo->ojrelid != 0)
+ {
+ /* PHV's value can be nulled at this join */
+ phv->phnullingrels = bms_add_member(phv->phnullingrels,
+ sjinfo->ojrelid);
+ }
+ }
+ else
+ {
+ /* It must be computed here. */
QualCost cost;
+ /* It'll start out not nulled by anything */
+ phv->phnullingrels = NULL;
+ /* Add the appropriate cost */
cost_qual_eval_node(&cost, (Node *) phinfo->ph_var->phexpr,
root);
joinrel->reltarget->cost.startup += cost.startup;
joinrel->reltarget->cost.per_tuple += cost.per_tuple;
}
+
+ joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs,
+ phv);
+ /* Update width estimate, too */
+ joinrel->reltarget->width += phinfo->ph_width;
}
/*
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 520409f4ba..5284ec367c 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -39,7 +39,7 @@ typedef struct JoinHashEntry
} JoinHashEntry;
static void build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
- RelOptInfo *input_rel);
+ RelOptInfo *input_rel, int ojrelid);
static List *build_joinrel_restrictlist(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outer_rel,
@@ -58,7 +58,8 @@ static void set_foreign_rel_properties(RelOptInfo *joinrel,
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);
+ SpecialJoinInfo *sjinfo,
+ List *restrictlist);
static bool have_partkey_equi_join(RelOptInfo *joinrel,
RelOptInfo *rel1, RelOptInfo *rel2,
JoinType jointype, List *restrictlist);
@@ -66,7 +67,8 @@ static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel,
bool strict_op);
static void set_joinrel_partition_key_exprs(RelOptInfo *joinrel,
RelOptInfo *outer_rel, RelOptInfo *inner_rel,
- JoinType jointype);
+ SpecialJoinInfo *sjinfo);
+static Node *add_nullingrel_to(Node *node, int relid);
static void build_child_join_reltarget(PlannerInfo *root,
RelOptInfo *parentrel,
RelOptInfo *childrel,
@@ -265,14 +267,9 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
*/
if (parent)
{
- /*
- * Each direct or indirect child wants to know the relids of its
- * topmost parent.
- */
- if (parent->top_parent_relids)
- rel->top_parent_relids = parent->top_parent_relids;
- else
- rel->top_parent_relids = bms_copy(parent->relids);
+ /* We keep back-links to immediate parent and topmost parent. */
+ rel->parent = parent;
+ rel->top_parent = parent->top_parent ? parent->top_parent : parent;
/*
* Also propagate lateral-reference information from appendrel parent
@@ -294,7 +291,8 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
}
else
{
- rel->top_parent_relids = NULL;
+ rel->parent = NULL;
+ rel->top_parent = NULL;
rel->direct_lateral_relids = NULL;
rel->lateral_relids = NULL;
rel->lateral_referencers = NULL;
@@ -369,7 +367,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)
@@ -390,6 +388,44 @@ find_base_rel(PlannerInfo *root, int relid)
return NULL; /* keep compiler quiet */
}
+/*
+ * 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.
@@ -663,7 +699,8 @@ build_join_rel(PlannerInfo *root,
joinrel->joininfo = NIL;
joinrel->has_eclass_joins = false;
joinrel->consider_partitionwise_join = false; /* might get changed later */
- joinrel->top_parent_relids = NULL;
+ joinrel->parent = NULL;
+ joinrel->top_parent = NULL;
joinrel->part_scheme = NULL;
joinrel->nparts = -1;
joinrel->boundinfo = NULL;
@@ -686,9 +723,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->jointype == JOIN_FULL) ? sjinfo->ojrelid : 0);
+ build_joinrel_tlist(root, joinrel, inner_rel,
+ (sjinfo->jointype != JOIN_INNER) ? sjinfo->ojrelid : 0);
+ add_placeholders_to_joinrel(root, joinrel, outer_rel, inner_rel, sjinfo);
/*
* add_placeholders_to_joinrel also took care of adding the ph_lateral
@@ -720,8 +759,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(joinrel, outer_rel, inner_rel, sjinfo,
+ restrictlist);
/*
* Set estimates of the joinrel's size.
@@ -777,16 +816,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;
@@ -800,6 +837,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);
@@ -842,7 +881,8 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
joinrel->joininfo = NIL;
joinrel->has_eclass_joins = false;
joinrel->consider_partitionwise_join = false; /* might get changed later */
- joinrel->top_parent_relids = NULL;
+ joinrel->parent = parent_joinrel;
+ joinrel->top_parent = parent_joinrel->top_parent ? parent_joinrel->top_parent : parent_joinrel;
joinrel->part_scheme = NULL;
joinrel->nparts = -1;
joinrel->boundinfo = NULL;
@@ -854,9 +894,6 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
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);
@@ -887,8 +924,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(joinrel, outer_rel, inner_rel, sjinfo,
+ restrictlist);
/* Child joinrel is parallel safe if parent is parallel safe. */
joinrel->consider_parallel = parent_joinrel->consider_parallel;
@@ -967,12 +1004,15 @@ min_join_parameterization(PlannerInfo *root,
* will still be needed above the join. This subroutine adds all such
* Vars from the specified input rel's tlist to the join rel's tlist.
*
+ * If the join can null Vars from this input relation, pass its RT index
+ * (if any) as ojrelid; if not, pass zero.
+ *
* 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().
*/
static void
build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
- RelOptInfo *input_rel)
+ RelOptInfo *input_rel, int ojrelid)
{
Relids relids = joinrel->relids;
ListCell *vars;
@@ -1003,9 +1043,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
@@ -1018,15 +1056,28 @@ 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 (ojrelid != 0)
+ {
+ var = copyObject(var);
+ var->varnullingrels = bms_add_member(var->varnullingrels, ojrelid);
}
+
+ joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs,
+ var);
+
+ /* Vars have cost zero, so no need to adjust reltarget->cost */
}
}
@@ -1045,7 +1096,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
@@ -1646,8 +1697,8 @@ find_param_path_info(RelOptInfo *rel, Relids required_outer)
*/
static void
build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel,
- RelOptInfo *inner_rel, List *restrictlist,
- JoinType jointype)
+ RelOptInfo *inner_rel, SpecialJoinInfo *sjinfo,
+ List *restrictlist)
{
PartitionScheme part_scheme;
@@ -1674,7 +1725,7 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel,
!inner_rel->consider_partitionwise_join ||
outer_rel->part_scheme != inner_rel->part_scheme ||
!have_partkey_equi_join(joinrel, outer_rel, inner_rel,
- jointype, restrictlist))
+ sjinfo->jointype, restrictlist))
{
Assert(!IS_PARTITIONED_REL(joinrel));
return;
@@ -1698,7 +1749,7 @@ 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);
/*
* Set the consider_partitionwise_join flag.
@@ -1878,6 +1929,23 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op)
{
if (equal(lfirst(lc), expr))
return cnt;
+
+ /*
+ * XXX For the moment, also allow a match if we have Vars that
+ * match except for varnullingrels. This may be indicative of a
+ * bug, although given the restriction to strict join operators,
+ * it could be okay.
+ */
+ if (IsA(expr, Var) && IsA(lfirst(lc), Var))
+ {
+ Var *v1 = (Var *) expr;
+ Var *v2 = (Var *) lfirst(lc);
+
+ if (v1->varno == v2->varno &&
+ v1->varattno == v2->varattno &&
+ v1->varlevelsup == v2->varlevelsup)
+ return cnt;
+ }
}
}
@@ -1891,7 +1959,7 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op)
static void
set_joinrel_partition_key_exprs(RelOptInfo *joinrel,
RelOptInfo *outer_rel, RelOptInfo *inner_rel,
- JoinType jointype)
+ SpecialJoinInfo *sjinfo)
{
PartitionScheme part_scheme = joinrel->part_scheme;
int partnatts = part_scheme->partnatts;
@@ -1917,7 +1985,7 @@ set_joinrel_partition_key_exprs(RelOptInfo *joinrel,
List *nullable_partexpr = NIL;
ListCell *lc;
- switch (jointype)
+ switch (sjinfo->jointype)
{
/*
* A join relation resulting from an INNER join may be
@@ -1993,18 +2061,37 @@ 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. However, we do
+ * have to worry about marking the COALESCE inputs as nullable
+ * by the full join, else these won't match the real thing.
*/
foreach(lc, list_concat_copy(outer_expr, outer_null_expr))
{
Node *larg = (Node *) lfirst(lc);
ListCell *lc2;
+ /* Insert nullingrel, or skip it if we can't */
+ larg = add_nullingrel_to(larg, sjinfo->ojrelid);
+ if (larg == NULL)
+ continue;
+
foreach(lc2, list_concat_copy(inner_expr, inner_null_expr))
{
Node *rarg = (Node *) lfirst(lc2);
- CoalesceExpr *c = makeNode(CoalesceExpr);
+ CoalesceExpr *c;
+
+ /* Forget it if coercions would be needed */
+ if (exprType(larg) != exprType(rarg) ||
+ exprCollation(larg) != exprCollation(rarg))
+ continue;
+ /* Insert nullingrel, or skip it if we can't */
+ rarg = add_nullingrel_to(rarg, sjinfo->ojrelid);
+ if (rarg == NULL)
+ continue;
+
+ /* Now we can build a valid merged join variable */
+ c = makeNode(CoalesceExpr);
c->coalescetype = exprType(larg);
c->coalescecollid = exprCollation(larg);
c->args = list_make2(larg, rarg);
@@ -2015,7 +2102,8 @@ set_joinrel_partition_key_exprs(RelOptInfo *joinrel,
break;
default:
- elog(ERROR, "unrecognized join type: %d", (int) jointype);
+ elog(ERROR, "unrecognized join type: %d",
+ (int) sjinfo->jointype);
}
joinrel->partexprs[cnt] = partexpr;
@@ -2023,6 +2111,54 @@ set_joinrel_partition_key_exprs(RelOptInfo *joinrel,
}
}
+/*
+ * Attempt to add relid to nullingrels of a FULL JOIN USING variable.
+ * Returns the modified expression if successful, or NULL if we failed.
+ *
+ * We currently don't support any cases where type coercion is involved,
+ * so only plain Vars and COALESCE nodes need be handled. However, we
+ * do need to support nested COALESCEs, so recursion is required.
+ */
+static Node *
+add_nullingrel_to(Node *node, int relid)
+{
+ if (IsA(node, Var))
+ {
+ /* Copy so we can modify it... */
+ Var *var = (Var *) copyObject(node);
+
+ /* ... and insert the correct nullingrel marker */
+ var->varnullingrels = bms_add_member(var->varnullingrels,
+ relid);
+ return (Node *) var;
+ }
+ if (IsA(node, CoalesceExpr))
+ {
+ CoalesceExpr *cexpr = (CoalesceExpr *) node;
+ CoalesceExpr *newcexpr;
+ List *newargs = NIL;
+ ListCell *lc;
+
+ /* Try to modify each argument ... */
+ foreach(lc, cexpr->args)
+ {
+ Node *newarg = add_nullingrel_to((Node *) lfirst(lc), relid);
+
+ if (newarg == NULL)
+ return NULL;
+ newargs = lappend(newargs, newarg);
+ }
+ /* Success, so make the result node */
+ newcexpr = makeNode(CoalesceExpr);
+ newcexpr->coalescetype = cexpr->coalescetype;
+ newcexpr->coalescecollid = cexpr->coalescecollid;
+ newcexpr->args = newargs;
+ newcexpr->location = cexpr->location;
+ return (Node *) newcexpr;
+ }
+ return NULL;
+}
+
/*
* build_child_join_reltarget
* Set up a child-join relation's reltarget from a parent-join relation.
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index ef8df3d098..6902c2a9d7 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -116,6 +116,7 @@ make_restrictinfo_internal(PlannerInfo *root,
Relids nullable_relids)
{
RestrictInfo *restrictinfo = makeNode(RestrictInfo);
+ Relids baserels;
restrictinfo->clause = clause;
restrictinfo->orclause = orclause;
@@ -187,6 +188,20 @@ make_restrictinfo_internal(PlannerInfo *root,
else
restrictinfo->required_relids = restrictinfo->clause_relids;
+ /*
+ * Count the number of base rels appearing in clause_relids. To do this,
+ * we just delete rels mentioned in root->outer_join_rels and count the
+ * survivors. Because we are called during deconstruct_jointree which is
+ * the same tree walk that populates outer_join_rels, this is a little bit
+ * unsafe-looking; but it should be fine because the recursion in
+ * deconstruct_jointree should already have visited any outer join that
+ * could be mentioned in this clause.
+ */
+ baserels = bms_difference(restrictinfo->clause_relids,
+ root->outer_join_rels);
+ restrictinfo->num_base_rels = bms_num_members(baserels);
+ bms_free(baserels);
+
/*
* Fill in all the cacheable fields with "not yet set" markers. None of
* these will be computed until/unless needed. Note in particular that we
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index ebc6ce84b0..81fc3002fe 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -62,6 +62,7 @@ typedef struct
typedef struct
{
+ PlannerInfo *root; /* could be NULL! */
Query *query; /* outer Query */
int sublevels_up;
bool possible_sublink; /* could aliases include a SubLink? */
@@ -80,6 +81,10 @@ static bool pull_var_clause_walker(Node *node,
pull_var_clause_context *context);
static Node *flatten_join_alias_vars_mutator(Node *node,
flatten_join_alias_vars_context *context);
+static Node *add_nullingrels_if_needed(PlannerInfo *root, Node *newnode,
+ Var *oldvar);
+static bool is_standard_join_alias_expression(Node *newnode, Var *oldvar);
+static void adjust_standard_join_alias_expression(Node *newnode, Var *oldvar);
static Relids alias_relid_set(Query *query, Relids relids);
@@ -88,6 +93,9 @@ static Relids alias_relid_set(Query *query, Relids relids);
* Create a set of all the distinct varnos present in a parsetree.
* Only varnos that reference level-zero rtable entries are considered.
*
+ * The result includes outer-join relids mentioned in Var.varnullingrels and
+ * PlaceHolderVar.phnullingrels fields in the parsetree.
+ *
* "root" can be passed as NULL if it is not necessary to process
* PlaceHolderVars.
*
@@ -153,7 +161,11 @@ pull_varnos_walker(Node *node, pull_varnos_context *context)
Var *var = (Var *) node;
if (var->varlevelsup == context->sublevels_up)
+ {
context->varnos = bms_add_member(context->varnos, var->varno);
+ context->varnos = bms_add_members(context->varnos,
+ var->varnullingrels);
+ }
return false;
}
if (IsA(node, CurrentOfExpr))
@@ -251,6 +263,14 @@ pull_varnos_walker(Node *node, pull_varnos_context *context)
context->varnos = bms_join(context->varnos,
newevalat);
}
+
+ /*
+ * In all three cases, include phnullingrels in the result. We
+ * don't worry about possibly needing to translate it, because
+ * appendrels only translate varnos of baserels, not outer joins.
+ */
+ context->varnos = bms_add_members(context->varnos,
+ phv->phnullingrels);
return false; /* don't recurse into expression */
}
}
@@ -714,26 +734,42 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context)
* is the only way that the executor can directly handle whole-row Vars.
*
* This also adjusts relid sets found in some expression node types to
- * substitute the contained base rels for any join relid.
+ * substitute the contained base+OJ rels for any join relid.
*
* If a JOIN contains sub-selects that have been flattened, its join alias
* entries might now be arbitrary expressions, not just Vars. This affects
- * this function in one important way: we might find ourselves inserting
- * SubLink expressions into subqueries, and we must make sure that their
- * Query.hasSubLinks fields get set to true if so. If there are any
+ * this function in two important ways. First, we might find ourselves
+ * inserting SubLink expressions into subqueries, and we must make sure that
+ * their Query.hasSubLinks fields get set to true if so. If there are any
* SubLinks in the join alias lists, the outer Query should already have
* hasSubLinks = true, so this is only relevant to un-flattened subqueries.
+ * Second, we have to preserve any varnullingrels info attached to the
+ * alias Vars we're replacing. If the replacement expression is a Var or
+ * PlaceHolderVar or constructed from those, we can just add the
+ * varnullingrels bits to the existing nullingrels field(s); otherwise
+ * we have to add a PlaceHolderVar wrapper.
*
- * NOTE: this is used on not-yet-planned expressions. We do not expect it
- * to be applied directly to the whole Query, so if we see a Query to start
- * with, we do want to increment sublevels_up (this occurs for LATERAL
- * subqueries).
+ * NOTE: this is also used by the parser, to expand join alias Vars before
+ * checking GROUP BY validity. For that use-case, root will be NULL, which
+ * is why we have to pass the Query separately. We need the root itself only
+ * for making PlaceHolderVars. We can avoid making PlaceHolderVars in the
+ * parser's usage because it won't be dealing with arbitrary expressions:
+ * so long as adjust_standard_join_alias_expression can handle everything
+ * the parser would make as a join alias expression, we're OK.
*/
Node *
-flatten_join_alias_vars(Query *query, Node *node)
+flatten_join_alias_vars(PlannerInfo *root, Query *query, Node *node)
{
flatten_join_alias_vars_context context;
+ /*
+ * We do not expect this to be applied to the whole Query, only to
+ * expressions or LATERAL subqueries. Hence, if the top node is a Query,
+ * it's okay to immediately increment sublevels_up.
+ */
+ Assert(node != (Node *) query);
+
+ context.root = root;
context.query = query;
context.sublevels_up = 0;
/* flag whether join aliases could possibly contain SubLinks */
@@ -804,7 +840,9 @@ flatten_join_alias_vars_mutator(Node *node,
rowexpr->colnames = colnames;
rowexpr->location = var->location;
- return (Node *) rowexpr;
+ /* Lastly, add any varnullingrels to the replacement expression */
+ return add_nullingrels_if_needed(context->root, (Node *) rowexpr,
+ var);
}
/* Expand join alias reference */
@@ -831,7 +869,8 @@ flatten_join_alias_vars_mutator(Node *node,
if (context->possible_sublink && !context->inserted_sublink)
context->inserted_sublink = checkExprHasSubLink(newvar);
- return newvar;
+ /* Lastly, add any varnullingrels to the replacement expression */
+ return add_nullingrels_if_needed(context->root, newvar, var);
}
if (IsA(node, PlaceHolderVar))
{
@@ -846,6 +885,7 @@ flatten_join_alias_vars_mutator(Node *node,
{
phv->phrels = alias_relid_set(context->query,
phv->phrels);
+ /* we *don't* change phnullingrels */
}
return (Node *) phv;
}
@@ -879,9 +919,145 @@ flatten_join_alias_vars_mutator(Node *node,
(void *) context);
}
+/*
+ * Add oldvar's varnullingrels, if any, to a flattened join alias expression.
+ * The newnode has been copied, so we can modify it freely.
+ */
+static Node *
+add_nullingrels_if_needed(PlannerInfo *root, Node *newnode, Var *oldvar)
+{
+ if (oldvar->varnullingrels == NULL)
+ return newnode; /* nothing to do */
+ /* If possible, do it by adding to existing nullingrel fields */
+ if (is_standard_join_alias_expression(newnode, oldvar))
+ adjust_standard_join_alias_expression(newnode, oldvar);
+ else if (root)
+ {
+ /* We can insert a PlaceHolderVar to carry the nullingrels */
+ PlaceHolderVar *newphv;
+ Relids phrels = pull_varnos(root, newnode);
+
+ /* XXX what if phrels is empty? */
+ Assert(!bms_is_empty(phrels)); /* probably wrong */
+ newphv = make_placeholder_expr(root, (Expr *) newnode, phrels);
+ /* newphv has zero phlevelsup and NULL phnullingrels; fix it */
+ newphv->phlevelsup = oldvar->varlevelsup;
+ newphv->phnullingrels = bms_copy(oldvar->varnullingrels);
+ newnode = (Node *) newphv;
+ }
+ else
+ {
+ /* ooops, we're missing support for something the parser can make */
+ elog(ERROR, "unsupported join alias expression");
+ }
+ return newnode;
+}
+
+/*
+ * Check to see if we can insert nullingrels into this join alias expression
+ * without use of a separate PlaceHolderVar.
+ *
+ * This will handle Vars, PlaceHolderVars, and implicit-coercion and COALESCE
+ * expressions built from those. This coverage needs to handle anything
+ * that the parser would put into joinaliasvars.
+ * XXX it's probably incomplete at the moment.
+ */
+static bool
+is_standard_join_alias_expression(Node *newnode, Var *oldvar)
+{
+ if (newnode == NULL)
+ return false;
+ if (IsA(newnode, Var) &&
+ ((Var *) newnode)->varlevelsup == oldvar->varlevelsup)
+ return true;
+ else if (IsA(newnode, PlaceHolderVar) &&
+ ((PlaceHolderVar *) newnode)->phlevelsup == oldvar->varlevelsup)
+ return true;
+ else if (IsA(newnode, FuncExpr))
+ {
+ FuncExpr *fexpr = (FuncExpr *) newnode;
+
+ /*
+ * We need to assume that the function wouldn't produce non-NULL from
+ * NULL, which is reasonable for implicit coercions but otherwise not
+ * so much. (Looking at its strictness is likely overkill, and anyway
+ * it would cause us to fail if someone forgot to mark an implicit
+ * coercion as strict.)
+ */
+ if (fexpr->funcformat != COERCE_IMPLICIT_CAST ||
+ fexpr->args == NIL)
+ return false;
+
+ /*
+ * Examine only the first argument --- coercions might have additional
+ * arguments that are constants.
+ */
+ return is_standard_join_alias_expression(linitial(fexpr->args), oldvar);
+ }
+ else if (IsA(newnode, CoalesceExpr))
+ {
+ CoalesceExpr *cexpr = (CoalesceExpr *) newnode;
+ ListCell *lc;
+
+ Assert(cexpr->args != NIL);
+ foreach(lc, cexpr->args)
+ {
+ if (!is_standard_join_alias_expression(lfirst(lc), oldvar))
+ return false;
+ }
+ return true;
+ }
+ else
+ return false;
+}
+
+/*
+ * Insert nullingrels into an expression accepted by
+ * is_standard_join_alias_expression.
+ */
+static void
+adjust_standard_join_alias_expression(Node *newnode, Var *oldvar)
+{
+ if (IsA(newnode, Var) &&
+ ((Var *) newnode)->varlevelsup == oldvar->varlevelsup)
+ {
+ Var *newvar = (Var *) newnode;
+
+ newvar->varnullingrels = bms_add_members(newvar->varnullingrels,
+ oldvar->varnullingrels);
+ }
+ else if (IsA(newnode, PlaceHolderVar) &&
+ ((PlaceHolderVar *) newnode)->phlevelsup == oldvar->varlevelsup)
+ {
+ PlaceHolderVar *newphv = (PlaceHolderVar *) newnode;
+
+ newphv->phnullingrels = bms_add_members(newphv->phnullingrels,
+ oldvar->varnullingrels);
+ }
+ else if (IsA(newnode, FuncExpr))
+ {
+ FuncExpr *fexpr = (FuncExpr *) newnode;
+
+ adjust_standard_join_alias_expression(linitial(fexpr->args), oldvar);
+ }
+ else if (IsA(newnode, CoalesceExpr))
+ {
+ CoalesceExpr *cexpr = (CoalesceExpr *) newnode;
+ ListCell *lc;
+
+ Assert(cexpr->args != NIL);
+ foreach(lc, cexpr->args)
+ {
+ adjust_standard_join_alias_expression(lfirst(lc), oldvar);
+ }
+ }
+ else
+ Assert(false);
+}
+
/*
* alias_relid_set: in a set of RT indexes, replace joins by their
- * underlying base relids
+ * underlying base+OJ relids
*/
static Relids
alias_relid_set(Query *query, Relids relids)
diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c
index 9d3c05aed3..aeabc2aca9 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -529,8 +529,8 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
partprunequal = (List *)
adjust_appendrel_attrs_multilevel(root,
(Node *) prunequal,
- subpart->relids,
- targetpart->relids);
+ subpart,
+ targetpart);
}
/*
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index fa1f589fad..e4033b1572 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -2204,7 +2204,7 @@ rowcomparesel(PlannerInfo *root,
else
{
/*
- * Otherwise, it's a join if there's more than one relation used.
+ * Otherwise, it's a join if there's more than one base relation used.
*/
is_join_clause = (NumRelids(root, (Node *) opargs) > 1);
}
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index fc808dcd27..5e80a741a4 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -23,13 +23,13 @@ extern AppendRelInfo *make_append_rel_info(Relation parentrel,
extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node,
int nappinfos, AppendRelInfo **appinfos);
extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
- Relids child_relids,
- Relids top_parent_relids);
+ RelOptInfo *childrel,
+ RelOptInfo *parentrel);
extern Relids adjust_child_relids(Relids relids, int nappinfos,
AppendRelInfo **appinfos);
extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
- Relids child_relids,
- Relids top_parent_relids);
+ RelOptInfo *childrel,
+ RelOptInfo *parentrel);
extern List *adjust_inherited_attnums(List *attnums, AppendRelInfo *context);
extern List *adjust_inherited_attnums_multilevel(PlannerInfo *root,
List *attnums,
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 7be1e5906b..1f5e0b24ca 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -202,6 +202,6 @@ extern bool contain_var_clause(Node *node);
extern bool contain_vars_of_level(Node *node, int levelsup);
extern int locate_var_of_level(Node *node, int levelsup);
extern List *pull_var_clause(Node *node, int flags);
-extern Node *flatten_join_alias_vars(Query *query, Node *node);
+extern Node *flatten_join_alias_vars(PlannerInfo *root, Query *query, Node *node);
#endif /* OPTIMIZER_H */
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index d2d46b15df..2dc4433985 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -302,6 +302,7 @@ extern void expand_planner_arrays(PlannerInfo *root, int add_size);
extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid,
RelOptInfo *parent);
extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid);
+extern RelOptInfo *find_base_rel_ignore_join(PlannerInfo *root, int relid);
extern RelOptInfo *find_join_rel(PlannerInfo *root, Relids relids);
extern RelOptInfo *build_join_rel(PlannerInfo *root,
Relids joinrelids,
@@ -333,6 +334,6 @@ extern ParamPathInfo *find_param_path_info(RelOptInfo *rel,
extern RelOptInfo *build_child_join_rel(PlannerInfo *root,
RelOptInfo *outer_rel, RelOptInfo *inner_rel,
RelOptInfo *parent_joinrel, List *restrictlist,
- SpecialJoinInfo *sjinfo, JoinType jointype);
+ SpecialJoinInfo *sjinfo);
#endif /* PATHNODE_H */
diff --git a/src/include/optimizer/placeholder.h b/src/include/optimizer/placeholder.h
index 39803ea41f..34b118a5c9 100644
--- a/src/include/optimizer/placeholder.h
+++ b/src/include/optimizer/placeholder.h
@@ -27,6 +27,7 @@ extern void update_placeholder_eval_levels(PlannerInfo *root,
extern void fix_placeholder_input_needed_levels(PlannerInfo *root);
extern void add_placeholders_to_base_rels(PlannerInfo *root);
extern void add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
- RelOptInfo *outer_rel, RelOptInfo *inner_rel);
+ RelOptInfo *outer_rel, RelOptInfo *inner_rel,
+ SpecialJoinInfo *sjinfo);
#endif /* PLACEHOLDER_H */
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index 2b11ff1d1f..ca03f32174 100644
--- a/src/include/optimizer/prep.h
+++ b/src/include/optimizer/prep.h
@@ -29,7 +29,8 @@ extern void pull_up_subqueries(PlannerInfo *root);
extern void flatten_simple_union_all(PlannerInfo *root);
extern void reduce_outer_joins(PlannerInfo *root);
extern void remove_useless_result_rtes(PlannerInfo *root);
-extern Relids get_relids_in_jointree(Node *jtnode, bool include_joins);
+extern Relids get_relids_in_jointree(Node *jtnode, bool include_outer_joins,
+ bool include_inner_joins);
extern Relids get_relids_for_join(Query *query, int joinrelid);
/*