summaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2023-01-30 13:44:36 -0500
committerTom Lane <tgl@sss.pgh.pa.us>2023-01-30 13:44:36 -0500
commitb448f1c8d83f8b65e2f0080c556ee21a7076da25 (patch)
treebc76c0506f01f224521b14304224598b8ba6699a /src/backend
parent2489d76c4906f4461a364ca8ad7e0751ead8aa0d (diff)
downloadpostgresql-b448f1c8d83f8b65e2f0080c556ee21a7076da25.tar.gz
Do assorted mop-up in the planner.
Remove RestrictInfo.nullable_relids, along with a good deal of infrastructure that calculated it. One use-case for it was in join_clause_is_movable_to, but we can now replace that usage with a check to see if the clause's relids include any outer join that can null the target relation. The other use-case was in join_clause_is_movable_into, but that test can just be dropped entirely now that the clause's relids include outer joins. Furthermore, join_clause_is_movable_into should now be accurate enough that it will accept anything returned by generate_join_implied_equalities, so we can restore the Assert that was diked out in commit 95f4e59c3. Remove the outerjoin_delayed mechanism. We needed this before to prevent quals from getting evaluated below outer joins that should null some of their vars. Now that we consider varnullingrels while placing quals, that's taken care of automatically, so throw the whole thing away. Teach remove_useless_result_rtes to also remove useless FromExprs. Having done that, the delay_upper_joins flag serves no purpose any more and we can remove it, largely reverting 11086f2f2. Use constant TRUE for "dummy" clauses when throwing back outer joins. This improves on a hack I introduced in commit 6a6522529. If we have a left-join clause l.x = r.y, and a WHERE clause l.x = constant, we generate r.y = constant and then don't really have a need for the join clause. But we must throw the join clause back anyway after marking it redundant, so that the join search heuristics won't think this is a clauseless join and avoid it. That was a kluge introduced under time pressure, and after looking at it I thought of a better way: let's just introduce constant-TRUE "join clauses" instead, and get rid of them at the end. This improves the generated plans for such cases by not having to test a redundant join clause. We can also get rid of the ugly hack used to mark such clauses as redundant for selectivity estimation. Patch by me; thanks to Richard Guo for review. Discussion: https://postgr.es/m/830269.1656693747@sss.pgh.pa.us
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/optimizer/path/allpaths.c1
-rw-r--r--src/backend/optimizer/path/clausesel.c6
-rw-r--r--src/backend/optimizer/path/costsize.c2
-rw-r--r--src/backend/optimizer/path/equivclass.c168
-rw-r--r--src/backend/optimizer/path/joinrels.c1
-rw-r--r--src/backend/optimizer/path/pathkeys.c40
-rw-r--r--src/backend/optimizer/plan/analyzejoins.c16
-rw-r--r--src/backend/optimizer/plan/initsplan.c371
-rw-r--r--src/backend/optimizer/plan/planner.c7
-rw-r--r--src/backend/optimizer/prep/prepjointree.c112
-rw-r--r--src/backend/optimizer/util/appendinfo.c3
-rw-r--r--src/backend/optimizer/util/inherit.c7
-rw-r--r--src/backend/optimizer/util/orclauses.c12
-rw-r--r--src/backend/optimizer/util/placeholder.c97
-rw-r--r--src/backend/optimizer/util/relnode.c21
-rw-r--r--src/backend/optimizer/util/restrictinfo.c123
16 files changed, 299 insertions, 688 deletions
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 26b294d5d0..ae0f9bdc8a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -2745,7 +2745,6 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
if (var)
pathkeys = build_expression_pathkey(root,
(Expr *) var,
- NULL, /* below outer joins */
Int8LessOperator,
rel->relids,
false);
diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index 61db6ad951..435438a173 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -716,12 +716,6 @@ clause_selectivity_ext(PlannerInfo *root,
}
/*
- * If the clause is marked redundant, always return 1.0.
- */
- if (rinfo->norm_selec > 1)
- return (Selectivity) 1.0;
-
- /*
* If possible, cache the result of the selectivity calculation for
* the clause. We can cache if varRelid is zero or the clause
* contains only vars of that relid --- otherwise varRelid will affect
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 7d957a47a4..7918bb6f0d 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -4787,7 +4787,6 @@ compute_semi_anti_join_factors(PlannerInfo *root,
norm_sjinfo.commute_below = NULL;
/* we don't bother trying to make the remaining fields valid */
norm_sjinfo.lhs_strict = false;
- norm_sjinfo.delay_upper_joins = false;
norm_sjinfo.semi_can_btree = false;
norm_sjinfo.semi_can_hash = false;
norm_sjinfo.semi_operators = NIL;
@@ -4956,7 +4955,6 @@ approx_tuple_count(PlannerInfo *root, JoinPath *path, List *quals)
sjinfo.commute_below = NULL;
/* we don't bother trying to make the remaining fields valid */
sjinfo.lhs_strict = false;
- sjinfo.delay_upper_joins = false;
sjinfo.semi_can_btree = false;
sjinfo.semi_can_hash = false;
sjinfo.semi_operators = NIL;
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 490953f2ff..007229d26c 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -34,7 +34,7 @@
static EquivalenceMember *add_eq_member(EquivalenceClass *ec,
- Expr *expr, Relids relids, Relids nullable_relids,
+ Expr *expr, Relids relids,
EquivalenceMember *parent,
Oid datatype);
static bool is_exprlist_member(Expr *node, List *exprs);
@@ -131,9 +131,7 @@ process_equivalence(PlannerInfo *root,
Expr *item1;
Expr *item2;
Relids item1_relids,
- item2_relids,
- item1_nullable_relids,
- item2_nullable_relids;
+ item2_relids;
List *opfamilies;
EquivalenceClass *ec1,
*ec2;
@@ -202,12 +200,10 @@ process_equivalence(PlannerInfo *root,
make_restrictinfo(root,
(Expr *) ntest,
restrictinfo->is_pushed_down,
- restrictinfo->outerjoin_delayed,
restrictinfo->pseudoconstant,
restrictinfo->security_level,
NULL,
- restrictinfo->outer_relids,
- restrictinfo->nullable_relids);
+ restrictinfo->outer_relids);
}
return false;
}
@@ -225,12 +221,6 @@ process_equivalence(PlannerInfo *root,
return false; /* RHS is non-strict but not constant */
}
- /* Calculate nullable-relid sets for each side of the clause */
- item1_nullable_relids = bms_intersect(item1_relids,
- restrictinfo->nullable_relids);
- item2_nullable_relids = bms_intersect(item2_relids,
- restrictinfo->nullable_relids);
-
/*
* We use the declared input types of the operator, not exprType() of the
* inputs, as the nominal datatypes for opfamily lookup. This presumes
@@ -400,7 +390,7 @@ process_equivalence(PlannerInfo *root,
else if (ec1)
{
/* Case 3: add item2 to ec1 */
- em2 = add_eq_member(ec1, item2, item2_relids, item2_nullable_relids,
+ em2 = add_eq_member(ec1, item2, item2_relids,
NULL, item2_type);
ec1->ec_sources = lappend(ec1->ec_sources, restrictinfo);
ec1->ec_below_outer_join |= below_outer_join;
@@ -418,7 +408,7 @@ process_equivalence(PlannerInfo *root,
else if (ec2)
{
/* Case 3: add item1 to ec2 */
- em1 = add_eq_member(ec2, item1, item1_relids, item1_nullable_relids,
+ em1 = add_eq_member(ec2, item1, item1_relids,
NULL, item1_type);
ec2->ec_sources = lappend(ec2->ec_sources, restrictinfo);
ec2->ec_below_outer_join |= below_outer_join;
@@ -452,9 +442,9 @@ process_equivalence(PlannerInfo *root,
ec->ec_min_security = restrictinfo->security_level;
ec->ec_max_security = restrictinfo->security_level;
ec->ec_merged = NULL;
- em1 = add_eq_member(ec, item1, item1_relids, item1_nullable_relids,
+ em1 = add_eq_member(ec, item1, item1_relids,
NULL, item1_type);
- em2 = add_eq_member(ec, item2, item2_relids, item2_nullable_relids,
+ em2 = add_eq_member(ec, item2, item2_relids,
NULL, item2_type);
root->eq_classes = lappend(root->eq_classes, ec);
@@ -545,13 +535,12 @@ canonicalize_ec_expression(Expr *expr, Oid req_type, Oid req_collation)
*/
static EquivalenceMember *
add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids,
- Relids nullable_relids, EquivalenceMember *parent, Oid datatype)
+ EquivalenceMember *parent, Oid datatype)
{
EquivalenceMember *em = makeNode(EquivalenceMember);
em->em_expr = expr;
em->em_relids = relids;
- em->em_nullable_relids = nullable_relids;
em->em_is_const = false;
em->em_is_child = (parent != NULL);
em->em_datatype = datatype;
@@ -588,13 +577,6 @@ add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids,
* equivalence class it is a member of; if none, optionally build a new
* single-member EquivalenceClass for it.
*
- * expr is the expression, and nullable_relids is the set of base relids
- * that are potentially nullable below it. We actually only care about
- * the set of such relids that are used in the expression; but for caller
- * convenience, we perform that intersection step here. The caller need
- * only be sure that nullable_relids doesn't omit any nullable rels that
- * might appear in the expr.
- *
* sortref is the SortGroupRef of the originating SortGroupClause, if any,
* or zero if not. (It should never be zero if the expression is volatile!)
*
@@ -623,7 +605,6 @@ add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids,
EquivalenceClass *
get_eclass_for_sort_expr(PlannerInfo *root,
Expr *expr,
- Relids nullable_relids,
List *opfamilies,
Oid opcintype,
Oid collation,
@@ -719,13 +700,12 @@ get_eclass_for_sort_expr(PlannerInfo *root,
elog(ERROR, "volatile EquivalenceClass has no sortref");
/*
- * Get the precise set of nullable relids appearing in the expression.
+ * Get the precise set of relids appearing in the expression.
*/
expr_relids = pull_varnos(root, (Node *) expr);
- nullable_relids = bms_intersect(nullable_relids, expr_relids);
newem = add_eq_member(newec, copyObject(expr), expr_relids,
- nullable_relids, NULL, opcintype);
+ NULL, opcintype);
/*
* add_eq_member doesn't check for volatile functions, set-returning
@@ -1163,11 +1143,8 @@ generate_base_implied_equalities_const(PlannerInfo *root,
{
RestrictInfo *restrictinfo = (RestrictInfo *) linitial(ec->ec_sources);
- if (bms_membership(restrictinfo->required_relids) != BMS_MULTIPLE)
- {
- distribute_restrictinfo_to_rels(root, restrictinfo);
- return;
- }
+ distribute_restrictinfo_to_rels(root, restrictinfo);
+ return;
}
/*
@@ -1211,8 +1188,6 @@ generate_base_implied_equalities_const(PlannerInfo *root,
rinfo = process_implied_equality(root, eq_op, ec->ec_collation,
cur_em->em_expr, const_em->em_expr,
bms_copy(ec->ec_relids),
- bms_union(cur_em->em_nullable_relids,
- const_em->em_nullable_relids),
ec->ec_min_security,
ec->ec_below_outer_join,
cur_em->em_is_const);
@@ -1285,8 +1260,6 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
rinfo = process_implied_equality(root, eq_op, ec->ec_collation,
prev_em->em_expr, cur_em->em_expr,
bms_copy(ec->ec_relids),
- bms_union(prev_em->em_nullable_relids,
- cur_em->em_nullable_relids),
ec->ec_min_security,
ec->ec_below_outer_join,
false);
@@ -1889,8 +1862,6 @@ create_join_clause(PlannerInfo *root,
rightem->em_expr,
bms_union(leftem->em_relids,
rightem->em_relids),
- bms_union(leftem->em_nullable_relids,
- rightem->em_nullable_relids),
ec->ec_min_security);
/* If it's a child clause, copy the parent's rinfo_serial */
@@ -1979,23 +1950,11 @@ create_join_clause(PlannerInfo *root,
* If we don't find any match for a set-aside outer join clause, we must
* throw it back into the regular joinclause processing by passing it to
* distribute_restrictinfo_to_rels(). If we do generate a derived clause,
- * however, the outer-join clause is redundant. We still throw it back,
- * because otherwise the join will be seen as a clauseless join and avoided
- * during join order searching; but we mark it as redundant to keep from
- * messing up the joinrel's size estimate. (This behavior means that the
- * API for this routine is uselessly complex: we could have just put all
- * the clauses into the regular processing initially. We keep it because
- * someday we might want to do something else, such as inserting "dummy"
- * joinclauses instead of real ones.)
- *
- * Outer join clauses that are marked outerjoin_delayed are special: this
- * condition means that one or both VARs might go to null due to a lower
- * outer join. We can still push a constant through the clause, but only
- * if its operator is strict; and we *have to* throw the clause back into
- * regular joinclause processing. By keeping the strict join clause,
- * we ensure that any null-extended rows that are mistakenly generated due
- * to suppressing rows not matching the constant will be rejected at the
- * upper outer join. (This doesn't work for full-join clauses.)
+ * however, the outer-join clause is redundant. We must still put some
+ * clause into the regular processing, because otherwise the join will be
+ * seen as a clauseless join and avoided during join order searching.
+ * We handle this by generating a constant-TRUE clause that is marked with
+ * required_relids that make it a join between the correct relations.
*/
void
reconsider_outer_join_clauses(PlannerInfo *root)
@@ -2021,10 +1980,14 @@ reconsider_outer_join_clauses(PlannerInfo *root)
/* remove it from the list */
root->left_join_clauses =
foreach_delete_current(root->left_join_clauses, cell);
- /* we throw it back anyway (see notes above) */
- /* but the thrown-back clause has no extra selectivity */
- rinfo->norm_selec = 2.0;
- rinfo->outer_selec = 1.0;
+ /* throw back a dummy replacement clause (see notes above) */
+ rinfo = make_restrictinfo(root,
+ (Expr *) makeBoolConst(true, false),
+ true, /* is_pushed_down */
+ false, /* pseudoconstant */
+ 0, /* security_level */
+ rinfo->required_relids,
+ rinfo->outer_relids);
distribute_restrictinfo_to_rels(root, rinfo);
}
}
@@ -2042,10 +2005,14 @@ reconsider_outer_join_clauses(PlannerInfo *root)
/* remove it from the list */
root->right_join_clauses =
foreach_delete_current(root->right_join_clauses, cell);
- /* we throw it back anyway (see notes above) */
- /* but the thrown-back clause has no extra selectivity */
- rinfo->norm_selec = 2.0;
- rinfo->outer_selec = 1.0;
+ /* throw back a dummy replacement clause (see notes above) */
+ rinfo = make_restrictinfo(root,
+ (Expr *) makeBoolConst(true, false),
+ true, /* is_pushed_down */
+ false, /* pseudoconstant */
+ 0, /* security_level */
+ rinfo->required_relids,
+ rinfo->outer_relids);
distribute_restrictinfo_to_rels(root, rinfo);
}
}
@@ -2063,10 +2030,14 @@ reconsider_outer_join_clauses(PlannerInfo *root)
/* remove it from the list */
root->full_join_clauses =
foreach_delete_current(root->full_join_clauses, cell);
- /* we throw it back anyway (see notes above) */
- /* but the thrown-back clause has no extra selectivity */
- rinfo->norm_selec = 2.0;
- rinfo->outer_selec = 1.0;
+ /* throw back a dummy replacement clause (see notes above) */
+ rinfo = make_restrictinfo(root,
+ (Expr *) makeBoolConst(true, false),
+ true, /* is_pushed_down */
+ false, /* pseudoconstant */
+ 0, /* security_level */
+ rinfo->required_relids,
+ rinfo->outer_relids);
distribute_restrictinfo_to_rels(root, rinfo);
}
}
@@ -2110,18 +2081,13 @@ reconsider_outer_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo,
left_type,
right_type,
inner_datatype;
- Relids inner_relids,
- inner_nullable_relids;
+ Relids inner_relids;
ListCell *lc1;
Assert(is_opclause(rinfo->clause));
opno = ((OpExpr *) rinfo->clause)->opno;
collation = ((OpExpr *) rinfo->clause)->inputcollid;
- /* If clause is outerjoin_delayed, operator must be strict */
- if (rinfo->outerjoin_delayed && !op_strict(opno))
- return false;
-
/* Extract needed info from the clause */
op_input_types(opno, &left_type, &right_type);
if (outer_on_left)
@@ -2138,8 +2104,6 @@ reconsider_outer_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo,
inner_datatype = left_type;
inner_relids = rinfo->left_relids;
}
- inner_nullable_relids = bms_intersect(inner_relids,
- rinfo->nullable_relids);
/* Scan EquivalenceClasses for a match to outervar */
foreach(lc1, root->eq_classes)
@@ -2200,7 +2164,6 @@ reconsider_outer_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo,
innervar,
cur_em->em_expr,
bms_copy(inner_relids),
- bms_copy(inner_nullable_relids),
cur_ec->ec_min_security);
if (process_equivalence(root, &newrinfo, true))
match = true;
@@ -2238,15 +2201,9 @@ reconsider_full_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo)
left_type,
right_type;
Relids left_relids,
- right_relids,
- left_nullable_relids,
- right_nullable_relids;
+ right_relids;
ListCell *lc1;
- /* Can't use an outerjoin_delayed clause here */
- if (rinfo->outerjoin_delayed)
- return false;
-
/* Extract needed info from the clause */
Assert(is_opclause(rinfo->clause));
opno = ((OpExpr *) rinfo->clause)->opno;
@@ -2256,10 +2213,6 @@ reconsider_full_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo)
rightvar = (Expr *) get_rightop(rinfo->clause);
left_relids = rinfo->left_relids;
right_relids = rinfo->right_relids;
- left_nullable_relids = bms_intersect(left_relids,
- rinfo->nullable_relids);
- right_nullable_relids = bms_intersect(right_relids,
- rinfo->nullable_relids);
foreach(lc1, root->eq_classes)
{
@@ -2361,7 +2314,6 @@ reconsider_full_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo)
leftvar,
cur_em->em_expr,
bms_copy(left_relids),
- bms_copy(left_nullable_relids),
cur_ec->ec_min_security);
if (process_equivalence(root, &newrinfo, true))
matchleft = true;
@@ -2377,7 +2329,6 @@ reconsider_full_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo)
rightvar,
cur_em->em_expr,
bms_copy(right_relids),
- bms_copy(right_nullable_relids),
cur_ec->ec_min_security);
if (process_equivalence(root, &newrinfo, true))
matchright = true;
@@ -2675,7 +2626,6 @@ add_child_rel_equivalences(PlannerInfo *root,
/* OK, generate transformed child version */
Expr *child_expr;
Relids new_relids;
- Relids new_nullable_relids;
if (parent_rel->reloptkind == RELOPT_BASEREL)
{
@@ -2705,21 +2655,7 @@ add_child_rel_equivalences(PlannerInfo *root,
top_parent_relids);
new_relids = bms_add_members(new_relids, child_relids);
- /*
- * And likewise for nullable_relids. Note this code assumes
- * parent and child relids are singletons.
- */
- new_nullable_relids = cur_em->em_nullable_relids;
- if (bms_overlap(new_nullable_relids, top_parent_relids))
- {
- new_nullable_relids = bms_difference(new_nullable_relids,
- top_parent_relids);
- new_nullable_relids = bms_add_members(new_nullable_relids,
- child_relids);
- }
-
- (void) add_eq_member(cur_ec, child_expr,
- new_relids, new_nullable_relids,
+ (void) add_eq_member(cur_ec, child_expr, new_relids,
cur_em, cur_em->em_datatype);
/* Record this EC index for the child rel */
@@ -2816,7 +2752,6 @@ add_child_join_rel_equivalences(PlannerInfo *root,
/* Yes, generate transformed child version */
Expr *child_expr;
Relids new_relids;
- Relids new_nullable_relids;
if (parent_joinrel->reloptkind == RELOPT_JOINREL)
{
@@ -2847,20 +2782,7 @@ add_child_join_rel_equivalences(PlannerInfo *root,
top_parent_relids);
new_relids = bms_add_members(new_relids, child_relids);
- /*
- * For nullable_relids, we must selectively replace parent
- * nullable relids with child ones.
- */
- new_nullable_relids = cur_em->em_nullable_relids;
- if (bms_overlap(new_nullable_relids, top_parent_relids))
- new_nullable_relids =
- adjust_child_relids_multilevel(root,
- new_nullable_relids,
- child_joinrel,
- child_joinrel->top_parent);
-
- (void) add_eq_member(cur_ec, child_expr,
- new_relids, new_nullable_relids,
+ (void) add_eq_member(cur_ec, child_expr, new_relids,
cur_em, cur_em->em_datatype);
}
}
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 56dd1073c5..d7cb11c851 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -743,7 +743,6 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
sjinfo->commute_below = NULL;
/* we don't bother trying to make the remaining fields valid */
sjinfo->lhs_strict = false;
- sjinfo->delay_upper_joins = false;
sjinfo->semi_can_btree = false;
sjinfo->semi_can_hash = false;
sjinfo->semi_operators = NIL;
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index d2e241c983..c4e7f97f68 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -180,9 +180,6 @@ pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys)
* Given an expression and sort-order information, create a PathKey.
* The result is always a "canonical" PathKey, but it might be redundant.
*
- * expr is the expression, and nullable_relids is the set of base relids
- * that are potentially nullable below it.
- *
* If the PathKey is being generated from a SortGroupClause, sortref should be
* the SortGroupClause's SortGroupRef; otherwise zero.
*
@@ -198,7 +195,6 @@ pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys)
static PathKey *
make_pathkey_from_sortinfo(PlannerInfo *root,
Expr *expr,
- Relids nullable_relids,
Oid opfamily,
Oid opcintype,
Oid collation,
@@ -234,7 +230,7 @@ make_pathkey_from_sortinfo(PlannerInfo *root,
equality_op);
/* Now find or (optionally) create a matching EquivalenceClass */
- eclass = get_eclass_for_sort_expr(root, expr, nullable_relids,
+ eclass = get_eclass_for_sort_expr(root, expr,
opfamilies, opcintype, collation,
sortref, rel, create_it);
@@ -257,7 +253,6 @@ make_pathkey_from_sortinfo(PlannerInfo *root,
static PathKey *
make_pathkey_from_sortop(PlannerInfo *root,
Expr *expr,
- Relids nullable_relids,
Oid ordering_op,
bool nulls_first,
Index sortref,
@@ -279,7 +274,6 @@ make_pathkey_from_sortop(PlannerInfo *root,
return make_pathkey_from_sortinfo(root,
expr,
- nullable_relids,
opfamily,
opcintype,
collation,
@@ -584,12 +578,10 @@ build_index_pathkeys(PlannerInfo *root,
}
/*
- * OK, try to make a canonical pathkey for this sort key. Note we're
- * underneath any outer joins, so nullable_relids should be NULL.
+ * OK, try to make a canonical pathkey for this sort key.
*/
cpathkey = make_pathkey_from_sortinfo(root,
indexkey,
- NULL,
index->sortopfamily[i],
index->opcintype[i],
index->indexcollations[i],
@@ -743,14 +735,12 @@ build_partition_pathkeys(PlannerInfo *root, RelOptInfo *partrel,
/*
* Try to make a canonical pathkey for this partkey.
*
- * We're considering a baserel scan, so nullable_relids should be
- * NULL. Also, we assume the PartitionDesc lists any NULL partition
- * last, so we treat the scan like a NULLS LAST index: we have
- * nulls_first for backwards scan only.
+ * We assume the PartitionDesc lists any NULL partition last, so we
+ * treat the scan like a NULLS LAST index: we have nulls_first for
+ * backwards scan only.
*/
cpathkey = make_pathkey_from_sortinfo(root,
keyCol,
- NULL,
partscheme->partopfamily[i],
partscheme->partopcintype[i],
partscheme->partcollation[i],
@@ -799,7 +789,7 @@ build_partition_pathkeys(PlannerInfo *root, RelOptInfo *partrel,
* Build a pathkeys list that describes an ordering by a single expression
* using the given sort operator.
*
- * expr, nullable_relids, and rel are as for make_pathkey_from_sortinfo.
+ * expr and rel are as for make_pathkey_from_sortinfo.
* We induce the other arguments assuming default sort order for the operator.
*
* Similarly to make_pathkey_from_sortinfo, the result is NIL if create_it
@@ -808,7 +798,6 @@ build_partition_pathkeys(PlannerInfo *root, RelOptInfo *partrel,
List *
build_expression_pathkey(PlannerInfo *root,
Expr *expr,
- Relids nullable_relids,
Oid opno,
Relids rel,
bool create_it)
@@ -827,7 +816,6 @@ build_expression_pathkey(PlannerInfo *root,
cpathkey = make_pathkey_from_sortinfo(root,
expr,
- nullable_relids,
opfamily,
opcintype,
exprCollation((Node *) expr),
@@ -908,14 +896,11 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* expression is *not* volatile in the outer query: it's just
* a Var referencing whatever the subquery emitted. (IOW, the
* outer query isn't going to re-execute the volatile
- * expression itself.) So this is okay. Likewise, it's
- * correct to pass nullable_relids = NULL, because we're
- * underneath any outer joins appearing in the outer query.
+ * expression itself.) So this is okay.
*/
outer_ec =
get_eclass_for_sort_expr(root,
(Expr *) outer_var,
- NULL,
sub_eclass->ec_opfamilies,
sub_member->em_datatype,
sub_eclass->ec_collation,
@@ -997,7 +982,6 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
/* See if we have a matching EC for the TLE */
outer_ec = get_eclass_for_sort_expr(root,
(Expr *) outer_var,
- NULL,
sub_eclass->ec_opfamilies,
sub_expr_type,
sub_expr_coll,
@@ -1138,13 +1122,6 @@ build_join_pathkeys(PlannerInfo *root,
* The resulting PathKeys are always in canonical form. (Actually, there
* is no longer any code anywhere that creates non-canonical PathKeys.)
*
- * We assume that root->nullable_baserels is the set of base relids that could
- * have gone to NULL below the SortGroupClause expressions. This is okay if
- * the expressions came from the query's top level (ORDER BY, DISTINCT, etc)
- * and if this function is only invoked after deconstruct_jointree. In the
- * future we might have to make callers pass in the appropriate
- * nullable-relids set, but for now it seems unnecessary.
- *
* 'sortclauses' is a list of SortGroupClause nodes
* 'tlist' is the targetlist to find the referenced tlist entries in
*/
@@ -1210,7 +1187,6 @@ make_pathkeys_for_sortclauses_extended(PlannerInfo *root,
}
pathkey = make_pathkey_from_sortop(root,
sortkey,
- root->nullable_baserels,
sortcl->sortop,
sortcl->nulls_first,
sortcl->tleSortGroupRef,
@@ -1268,7 +1244,6 @@ initialize_mergeclause_eclasses(PlannerInfo *root, RestrictInfo *restrictinfo)
restrictinfo->left_ec =
get_eclass_for_sort_expr(root,
(Expr *) get_leftop(clause),
- restrictinfo->nullable_relids,
restrictinfo->mergeopfamilies,
lefttype,
((OpExpr *) clause)->inputcollid,
@@ -1278,7 +1253,6 @@ initialize_mergeclause_eclasses(PlannerInfo *root, RestrictInfo *restrictinfo)
restrictinfo->right_ec =
get_eclass_for_sort_expr(root,
(Expr *) get_rightop(clause),
- restrictinfo->nullable_relids,
restrictinfo->mergeopfamilies,
righttype,
((OpExpr *) clause)->inputcollid,
diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index fbb652e7b0..42da62c1c6 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -170,11 +170,10 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo)
int attroff;
/*
- * Must be a non-delaying left join to a single baserel, else we aren't
- * going to be able to do anything with it.
+ * Must be a left join to a single baserel, else we aren't going to be
+ * able to do anything with it.
*/
- if (sjinfo->jointype != JOIN_LEFT ||
- sjinfo->delay_upper_joins)
+ if (sjinfo->jointype != JOIN_LEFT)
return false;
if (!bms_get_singleton_member(sjinfo->min_righthand, &innerrelid))
@@ -570,13 +569,10 @@ reduce_unique_semijoins(PlannerInfo *root)
List *restrictlist;
/*
- * Must be a non-delaying semijoin to a single baserel, else we aren't
- * going to be able to do anything with it. (It's probably not
- * possible for delay_upper_joins to be set on a semijoin, but we
- * might as well check.)
+ * Must be a semijoin to a single baserel, else we aren't going to be
+ * able to do anything with it.
*/
- if (sjinfo->jointype != JOIN_SEMI ||
- sjinfo->delay_upper_joins)
+ if (sjinfo->jointype != JOIN_SEMI)
continue;
if (!bms_get_singleton_member(sjinfo->min_righthand, &innerrelid))
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 7db8291549..0f4163bffd 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -94,6 +94,8 @@ static void deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem,
static void process_security_barrier_quals(PlannerInfo *root,
int rti, Relids qualscope,
bool below_outer_join);
+static void mark_rels_nulled_by_join(PlannerInfo *root, Index ojrelid,
+ Relids lower_rels);
static SpecialJoinInfo *make_outerjoininfo(PlannerInfo *root,
Relids left_rels, Relids right_rels,
Relids inner_join_rels,
@@ -128,10 +130,6 @@ static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
bool is_clone,
List **postponed_qual_list,
List **postponed_oj_qual_list);
-static bool check_outerjoin_delay(PlannerInfo *root, Relids *relids_p,
- Relids *nullable_relids_p, bool is_pushed_down);
-static bool check_equivalence_delay(PlannerInfo *root,
- RestrictInfo *restrictinfo);
static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause);
static void check_mergejoinable(RestrictInfo *restrictinfo);
static void check_hashjoinable(RestrictInfo *restrictinfo);
@@ -737,15 +735,6 @@ create_lateral_join_info(PlannerInfo *root)
* A sub-joinlist represents a subproblem to be planned separately. Currently
* sub-joinlists arise only from FULL OUTER JOIN or when collapsing of
* subproblems is stopped by join_collapse_limit or from_collapse_limit.
- *
- * NOTE: when dealing with inner joins, it is appropriate to let a qual clause
- * be evaluated at the lowest level where all the variables it mentions are
- * available. However, we cannot push a qual down into the nullable side(s)
- * of an outer join since the qual might eliminate matching rows and cause a
- * NULL row to be incorrectly emitted by the join. Therefore, we artificially
- * OR the minimum-relids of such an outer join into the required_relids of
- * clauses appearing above it. This forces those clauses to be delayed until
- * application of the outer join (or maybe even higher in the join tree).
*/
List *
deconstruct_jointree(PlannerInfo *root)
@@ -757,9 +746,8 @@ deconstruct_jointree(PlannerInfo *root)
/*
* After this point, no more PlaceHolderInfos may be made, because
- * make_outerjoininfo and update_placeholder_eval_levels require all
- * active placeholders to be present in root->placeholder_list while we
- * crawl up the join tree.
+ * make_outerjoininfo requires all active placeholders to be present in
+ * root->placeholder_list while we crawl up the join tree.
*/
root->placeholdersFrozen = true;
@@ -770,7 +758,6 @@ deconstruct_jointree(PlannerInfo *root)
/* These are filled as we scan the jointree */
root->all_baserels = NULL;
root->outer_join_rels = NULL;
- root->nullable_baserels = NULL;
/* Perform the initial scan of the jointree */
result = deconstruct_recurse(root, (Node *) root->parse->jointree,
@@ -798,31 +785,12 @@ deconstruct_jointree(PlannerInfo *root)
*/
if (root->join_info_list)
{
- /*
- * XXX hack: when we call distribute_qual_to_rels to process one of
- * these clauses, neither the owning SpecialJoinInfo nor any later
- * ones can appear in root->join_info_list, else the wrong things will
- * happen. Fake it out by emptying join_info_list and rebuilding it
- * as we go. This works because join_info_list is only appended to
- * during deconstruct_distribute, so we know we are examining
- * SpecialJoinInfos bottom-up, just like the first time. We can get
- * rid of this hack later, after fixing things so that
- * distribute_qual_to_rels doesn't have that requirement about
- * join_info_list.
- */
- root->join_info_list = NIL;
-
foreach(lc, item_list)
{
JoinTreeItem *jtitem = (JoinTreeItem *) lfirst(lc);
if (jtitem->oj_joinclauses != NIL)
deconstruct_distribute_oj_quals(root, item_list, jtitem);
-
- /* XXX Rest of hack: rebuild join_info_list as we go */
- if (jtitem->sjinfo)
- root->join_info_list = lappend(root->join_info_list,
- jtitem->sjinfo);
}
}
@@ -926,7 +894,6 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode,
else if (IsA(jtnode, JoinExpr))
{
JoinExpr *j = (JoinExpr *) jtnode;
- Relids nullable_rels;
JoinTreeItem *left_item,
*right_item;
List *leftjoinlist,
@@ -952,8 +919,6 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode,
jtitem->right_rels = right_item->qualscope;
/* Inner join adds no restrictions for quals */
jtitem->nonnullable_rels = NULL;
- /* and it doesn't force anything to null, either */
- nullable_rels = NULL;
break;
case JOIN_LEFT:
case JOIN_ANTI:
@@ -976,13 +941,14 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode,
j->rtindex);
root->outer_join_rels = bms_add_member(root->outer_join_rels,
j->rtindex);
+ mark_rels_nulled_by_join(root, j->rtindex,
+ right_item->qualscope);
}
jtitem->inner_join_rels = bms_union(left_item->inner_join_rels,
right_item->inner_join_rels);
jtitem->left_rels = left_item->qualscope;
jtitem->right_rels = right_item->qualscope;
jtitem->nonnullable_rels = left_item->qualscope;
- nullable_rels = right_item->qualscope;
break;
case JOIN_SEMI:
/* Recurse */
@@ -1005,13 +971,6 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode,
jtitem->right_rels = right_item->qualscope;
/* Semi join adds no restrictions for quals */
jtitem->nonnullable_rels = NULL;
-
- /*
- * Theoretically, a semijoin would null the RHS; but since the
- * RHS can't be accessed above the join, this is immaterial
- * and we needn't account for it.
- */
- nullable_rels = NULL;
break;
case JOIN_FULL:
/* Recurse */
@@ -1031,27 +990,25 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode,
j->rtindex);
root->outer_join_rels = bms_add_member(root->outer_join_rels,
j->rtindex);
+ mark_rels_nulled_by_join(root, j->rtindex,
+ left_item->qualscope);
+ mark_rels_nulled_by_join(root, j->rtindex,
+ right_item->qualscope);
jtitem->inner_join_rels = bms_union(left_item->inner_join_rels,
right_item->inner_join_rels);
jtitem->left_rels = left_item->qualscope;
jtitem->right_rels = right_item->qualscope;
/* each side is both outer and inner */
jtitem->nonnullable_rels = jtitem->qualscope;
- nullable_rels = jtitem->qualscope;
break;
default:
/* JOIN_RIGHT was eliminated during reduce_outer_joins() */
elog(ERROR, "unrecognized join type: %d",
(int) j->jointype);
leftjoinlist = rightjoinlist = NIL; /* keep compiler quiet */
- nullable_rels = NULL;
break;
}
- /* Report all rels that will be nulled anywhere in the jointree */
- root->nullable_baserels = bms_add_members(root->nullable_baserels,
- nullable_rels);
-
/*
* Compute the output joinlist. We fold subproblems together except
* at a FULL JOIN or where join_collapse_limit would be exceeded.
@@ -1276,11 +1233,7 @@ deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem,
/* And add the SpecialJoinInfo to join_info_list */
if (sjinfo)
- {
root->join_info_list = lappend(root->join_info_list, sjinfo);
- /* Each time we do that, recheck placeholder eval levels */
- update_placeholder_eval_levels(root, sjinfo);
- }
}
else
{
@@ -1346,6 +1299,33 @@ process_security_barrier_quals(PlannerInfo *root,
}
/*
+ * mark_rels_nulled_by_join
+ * Fill RelOptInfo.nulling_relids of baserels nulled by this outer join
+ *
+ * Inputs:
+ * ojrelid: RT index of the join RTE (must not be 0)
+ * lower_rels: the base+OJ Relids syntactically below nullable side of join
+ */
+static void
+mark_rels_nulled_by_join(PlannerInfo *root, Index ojrelid,
+ Relids lower_rels)
+{
+ int relid = -1;
+
+ while ((relid = bms_next_member(lower_rels, relid)) > 0)
+ {
+ RelOptInfo *rel = root->simple_rel_array[relid];
+
+ if (rel == NULL) /* must be an outer join */
+ {
+ Assert(bms_is_member(relid, root->outer_join_rels));
+ continue;
+ }
+ rel->nulling_relids = bms_add_member(rel->nulling_relids, ojrelid);
+ }
+}
+
+/*
* make_outerjoininfo
* Build a SpecialJoinInfo for the current outer join
*
@@ -1422,8 +1402,6 @@ make_outerjoininfo(PlannerInfo *root,
sjinfo->commute_above_l = NULL;
sjinfo->commute_above_r = NULL;
sjinfo->commute_below = NULL;
- /* this always starts out false */
- sjinfo->delay_upper_joins = false;
compute_semijoin_info(root, sjinfo, clause);
@@ -1578,17 +1556,6 @@ make_outerjoininfo(PlannerInfo *root,
* Also, we must preserve ordering anyway if we have unsafe PHVs, or
* if either this join or the lower OJ is a semijoin or antijoin.
*
- * Here, we have to consider that "our join condition" includes any
- * clauses that syntactically appeared above the lower OJ and below
- * ours; those are equivalent to degenerate clauses in our OJ and must
- * be treated as such. Such clauses obviously can't reference our
- * LHS, and they must be non-strict for the lower OJ's RHS (else
- * reduce_outer_joins would have reduced the lower OJ to a plain
- * join). Hence the other ways in which we handle clauses within our
- * join condition are not affected by them. The net effect is
- * therefore sufficiently represented by the delay_upper_joins flag
- * saved for us by check_outerjoin_delay.
- *
* When we don't need to preserve ordering, check to see if outer join
* identity 3 applies, and if so, remove the lower OJ's ojrelid from
* our min_righthand so that commutation is allowed.
@@ -1602,7 +1569,7 @@ make_outerjoininfo(PlannerInfo *root,
jointype == JOIN_ANTI ||
otherinfo->jointype == JOIN_SEMI ||
otherinfo->jointype == JOIN_ANTI ||
- !otherinfo->lhs_strict || otherinfo->delay_upper_joins)
+ !otherinfo->lhs_strict)
{
/* Preserve ordering */
min_righthand = bms_add_members(min_righthand,
@@ -2152,8 +2119,8 @@ distribute_quals_to_rels(PlannerInfo *root, List *clauses,
* 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;
- * in particular, the passed-in SpecialJoinInfo isn't yet in that list.
+ * at least those special joins that are syntactically below this qual.
+ * (We now need that only for detection of redundant IS NULL quals.)
*/
static void
distribute_qual_to_rels(PlannerInfo *root, Node *clause,
@@ -2171,11 +2138,9 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
{
Relids relids;
bool is_pushed_down;
- bool outerjoin_delayed;
bool pseudoconstant = false;
bool maybe_equivalence;
bool maybe_outer_join;
- Relids nullable_relids;
RestrictInfo *restrictinfo;
/*
@@ -2326,21 +2291,12 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
maybe_equivalence = false;
maybe_outer_join = true;
- /* Check to see if must be delayed by lower outer join */
- outerjoin_delayed = check_outerjoin_delay(root,
- &relids,
- &nullable_relids,
- false);
-
/*
* Now force the qual to be evaluated exactly at the level of joining
* corresponding to the outer join. We cannot let it get pushed down
* into the nonnullable side, since then we'd produce no output rows,
* rather than the intended single null-extended row, for any
* nonnullable-side rows failing the qual.
- *
- * (Do this step after calling check_outerjoin_delay, because that
- * trashes relids.)
*/
Assert(ojscope);
relids = ojscope;
@@ -2354,34 +2310,16 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
*/
is_pushed_down = true;
- /* Check to see if must be delayed by lower outer join */
- outerjoin_delayed = check_outerjoin_delay(root,
- &relids,
- &nullable_relids,
- true);
-
- if (outerjoin_delayed)
- {
- /* Should still be a subset of current scope ... */
- Assert(root->hasLateralRTEs || bms_is_subset(relids, qualscope));
- Assert(ojscope == NULL || bms_is_subset(relids, ojscope));
-
- /*
- * Because application of the qual will be delayed by outer join,
- * we mustn't assume its vars are equal everywhere.
- */
- maybe_equivalence = false;
+ /*
+ * It's possible that this is an IS NULL clause that's redundant with
+ * a lower antijoin; if so we can just discard it. We need not test
+ * in any of the other cases, because this will only be possible for
+ * pushed-down clauses.
+ */
+ if (check_redundant_nullability_qual(root, clause))
+ return;
- /*
- * It's possible that this is an IS NULL clause that's redundant
- * with a lower antijoin; if so we can just discard it. We need
- * not test in any of the other cases, because this will only be
- * possible for pushed-down, delayed clauses.
- */
- if (check_redundant_nullability_qual(root, clause))
- return;
- }
- else if (!allow_equivalence)
+ if (!allow_equivalence)
{
/* Caller says it mustn't become an equivalence class */
maybe_equivalence = false;
@@ -2389,8 +2327,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
else
{
/*
- * Qual is not delayed by any lower outer-join restriction, so we
- * can consider feeding it to the equivalence machinery. However,
+ * Consider feeding qual to the equivalence machinery. However,
* if it's itself within an outer-join clause, treat it as though
* it appeared below that outer join (note that we can only get
* here when the clause references only nullable-side rels).
@@ -2413,12 +2350,10 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
restrictinfo = make_restrictinfo(root,
(Expr *) clause,
is_pushed_down,
- outerjoin_delayed,
pseudoconstant,
security_level,
relids,
- outerjoin_nonnullable,
- nullable_relids);
+ outerjoin_nonnullable);
/* Apply appropriate clone marking, too */
restrictinfo->has_clone = has_clone;
@@ -2466,6 +2401,8 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
check_mergejoinable(restrictinfo);
/*
+ * XXX rewrite:
+ *
* If it is a true equivalence clause, send it to the EquivalenceClass
* machinery. We do *not* attach it directly to any restriction or join
* lists. The EC code will propagate it to the appropriate places later.
@@ -2501,8 +2438,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
{
if (maybe_equivalence)
{
- if (check_equivalence_delay(root, restrictinfo) &&
- process_equivalence(root, &restrictinfo, below_outer_join))
+ if (process_equivalence(root, &restrictinfo, below_outer_join))
return;
/* EC rejected it, so set left_ec/right_ec the hard way ... */
if (restrictinfo->mergeopfamilies) /* EC might have changed this */
@@ -2568,165 +2504,6 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
}
/*
- * check_outerjoin_delay
- * Detect whether a qual referencing the given relids must be delayed
- * in application due to the presence of a lower outer join, and/or
- * may force extra delay of higher-level outer joins.
- *
- * If the qual must be delayed, add relids to *relids_p to reflect the lowest
- * safe level for evaluating the qual, and return true. Any extra delay for
- * higher-level joins is reflected by setting delay_upper_joins to true in
- * SpecialJoinInfo structs. We also compute nullable_relids, the set of
- * referenced relids that are nullable by lower outer joins (note that this
- * can be nonempty even for a non-delayed qual).
- *
- * For an is_pushed_down qual, we can evaluate the qual as soon as (1) we have
- * all the rels it mentions, and (2) we are at or above any outer joins that
- * can null any of these rels and are below the syntactic location of the
- * given qual. We must enforce (2) because pushing down such a clause below
- * the OJ might cause the OJ to emit null-extended rows that should not have
- * been formed, or that should have been rejected by the clause. (This is
- * only an issue for non-strict quals, since if we can prove a qual mentioning
- * only nullable rels is strict, we'd have reduced the outer join to an inner
- * join in reduce_outer_joins().)
- *
- * To enforce (2), scan the join_info_list and merge the required-relid sets of
- * any such OJs into the clause's own reference list. At the time we are
- * called, the join_info_list contains only outer joins below this qual. We
- * have to repeat the scan until no new relids get added; this ensures that
- * the qual is suitably delayed regardless of the order in which OJs get
- * executed. As an example, if we have one OJ with LHS=A, RHS=B, and one with
- * LHS=B, RHS=C, it is implied that these can be done in either order; if the
- * B/C join is done first then the join to A can null C, so a qual actually
- * mentioning only C cannot be applied below the join to A.
- *
- * For a non-pushed-down qual, this isn't going to determine where we place the
- * qual, but we need to determine outerjoin_delayed and nullable_relids anyway
- * for use later in the planning process.
- *
- * Lastly, a pushed-down qual that references the nullable side of any current
- * join_info_list member and has to be evaluated above that OJ (because its
- * required relids overlap the LHS too) causes that OJ's delay_upper_joins
- * flag to be set true. This will prevent any higher-level OJs from
- * being interchanged with that OJ, which would result in not having any
- * correct place to evaluate the qual. (The case we care about here is a
- * sub-select WHERE clause within the RHS of some outer join. The WHERE
- * clause must effectively be treated as a degenerate clause of that outer
- * join's condition. Rather than trying to match such clauses with joins
- * directly, we set delay_upper_joins here, and when the upper outer join
- * is processed by make_outerjoininfo, it will refrain from allowing the
- * two OJs to commute.)
- */
-static bool
-check_outerjoin_delay(PlannerInfo *root,
- Relids *relids_p, /* in/out parameter */
- Relids *nullable_relids_p, /* output parameter */
- bool is_pushed_down)
-{
- Relids relids;
- Relids nullable_relids;
- bool outerjoin_delayed;
- bool found_some;
-
- /* fast path if no special joins */
- if (root->join_info_list == NIL)
- {
- *nullable_relids_p = NULL;
- return false;
- }
-
- /* must copy relids because we need the original value at the end */
- relids = bms_copy(*relids_p);
- nullable_relids = NULL;
- outerjoin_delayed = false;
- do
- {
- ListCell *l;
-
- found_some = false;
- foreach(l, root->join_info_list)
- {
- SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(l);
-
- /* do we reference any nullable rels of this OJ? */
- if (bms_overlap(relids, sjinfo->min_righthand) ||
- (sjinfo->jointype == JOIN_FULL &&
- bms_overlap(relids, sjinfo->min_lefthand)))
- {
- /* yes; have we included all its rels in relids? */
- if (!bms_is_subset(sjinfo->min_lefthand, relids) ||
- !bms_is_subset(sjinfo->min_righthand, relids))
- {
- /* no, so add them in */
- relids = bms_add_members(relids, sjinfo->min_lefthand);
- relids = bms_add_members(relids, sjinfo->min_righthand);
- outerjoin_delayed = true;
- /* we'll need another iteration */
- found_some = true;
- }
- /* track all the nullable rels of relevant OJs */
- nullable_relids = bms_add_members(nullable_relids,
- sjinfo->min_righthand);
- if (sjinfo->jointype == JOIN_FULL)
- nullable_relids = bms_add_members(nullable_relids,
- sjinfo->min_lefthand);
- /* set delay_upper_joins if needed */
- if (is_pushed_down && sjinfo->jointype != JOIN_FULL &&
- bms_overlap(relids, sjinfo->min_lefthand))
- sjinfo->delay_upper_joins = true;
- }
- }
- } while (found_some);
-
- /* identify just the actually-referenced nullable rels */
- nullable_relids = bms_int_members(nullable_relids, *relids_p);
-
- /* replace *relids_p, and return nullable_relids */
- bms_free(*relids_p);
- *relids_p = relids;
- *nullable_relids_p = nullable_relids;
- return outerjoin_delayed;
-}
-
-/*
- * check_equivalence_delay
- * Detect whether a potential equivalence clause is rendered unsafe
- * by outer-join-delay considerations. Return true if it's safe.
- *
- * The initial tests in distribute_qual_to_rels will consider a mergejoinable
- * clause to be a potential equivalence clause if it is not outerjoin_delayed.
- * But since the point of equivalence processing is that we will recombine the
- * two sides of the clause with others, we have to check that each side
- * satisfies the not-outerjoin_delayed condition on its own; otherwise it might
- * not be safe to evaluate everywhere we could place a derived equivalence
- * condition.
- */
-static bool
-check_equivalence_delay(PlannerInfo *root,
- RestrictInfo *restrictinfo)
-{
- Relids relids;
- Relids nullable_relids;
-
- /* fast path if no special joins */
- if (root->join_info_list == NIL)
- return true;
-
- /* must copy restrictinfo's relids to avoid changing it */
- relids = bms_copy(restrictinfo->left_relids);
- /* check left side does not need delay */
- if (check_outerjoin_delay(root, &relids, &nullable_relids, true))
- return false;
-
- /* and similarly for the right side */
- relids = bms_copy(restrictinfo->right_relids);
- if (check_outerjoin_delay(root, &relids, &nullable_relids, true))
- return false;
-
- return true;
-}
-
-/*
* check_redundant_nullability_qual
* Check to see if the qual is an IS NULL qual that is redundant with
* a lower JOIN_ANTI join.
@@ -2740,25 +2517,33 @@ static bool
check_redundant_nullability_qual(PlannerInfo *root, Node *clause)
{
Var *forced_null_var;
- Index forced_null_rel;
ListCell *lc;
/* Check for IS NULL, and identify the Var forced to NULL */
forced_null_var = find_forced_null_var(clause);
if (forced_null_var == NULL)
return false;
- forced_null_rel = forced_null_var->varno;
/*
* If the Var comes from the nullable side of a lower antijoin, the IS
- * NULL condition is necessarily true.
+ * NULL condition is necessarily true. If it's not nulled by anything,
+ * there is no point in searching the join_info_list. Otherwise, we need
+ * to find out whether the nulling rel is an antijoin.
*/
+ if (forced_null_var->varnullingrels == NULL)
+ return false;
+
foreach(lc, root->join_info_list)
{
SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc);
- if (sjinfo->jointype == JOIN_ANTI &&
- bms_is_member(forced_null_rel, sjinfo->syn_righthand))
+ /*
+ * This test will not succeed if sjinfo->ojrelid is zero, which is
+ * possible for an antijoin that was converted from a semijoin; but in
+ * such a case the Var couldn't have come from its nullable side.
+ */
+ if (sjinfo->jointype == JOIN_ANTI && sjinfo->ojrelid != 0 &&
+ bms_is_member(sjinfo->ojrelid, forced_null_var->varnullingrels))
return true;
}
@@ -2846,11 +2631,6 @@ distribute_restrictinfo_to_rels(PlannerInfo *root,
* variable-free. Otherwise the qual is applied at the lowest join level
* that provides all its variables.
*
- * "nullable_relids" is the set of relids used in the expressions that are
- * potentially nullable below the expressions. (This has to be supplied by
- * caller because this function is used after deconstruct_jointree, so we
- * don't have knowledge of where the clause items came from.)
- *
* "security_level" is the security level to assign to the new restrictinfo.
*
* "both_const" indicates whether both items are known pseudo-constant;
@@ -2876,7 +2656,6 @@ process_implied_equality(PlannerInfo *root,
Expr *item1,
Expr *item2,
Relids qualscope,
- Relids nullable_relids,
Index security_level,
bool below_outer_join,
bool both_const)
@@ -2956,12 +2735,10 @@ process_implied_equality(PlannerInfo *root,
restrictinfo = make_restrictinfo(root,
(Expr *) clause,
true, /* is_pushed_down */
- false, /* outerjoin_delayed */
pseudoconstant,
security_level,
relids,
- NULL, /* outer_relids */
- nullable_relids);
+ NULL); /* outer_relids */
/*
* If it's a join clause, add vars used in the clause to targetlists of
@@ -3026,7 +2803,6 @@ build_implied_join_equality(PlannerInfo *root,
Expr *item1,
Expr *item2,
Relids qualscope,
- Relids nullable_relids,
Index security_level)
{
RestrictInfo *restrictinfo;
@@ -3050,12 +2826,10 @@ build_implied_join_equality(PlannerInfo *root,
restrictinfo = make_restrictinfo(root,
clause,
true, /* is_pushed_down */
- false, /* outerjoin_delayed */
false, /* pseudoconstant */
security_level, /* security_level */
qualscope, /* required_relids */
- NULL, /* outer_relids */
- nullable_relids); /* nullable_relids */
+ NULL); /* outer_relids */
/* Set mergejoinability/hashjoinability flags */
check_mergejoinable(restrictinfo);
@@ -3123,8 +2897,7 @@ match_foreign_keys_to_quals(PlannerInfo *root)
* Note: for simple inner joins, any match should be in an eclass.
* "Loose" quals that syntactically match an FK equality must have
* been rejected for EC status because they are outer-join quals or
- * similar. We can still consider them to match the FK if they are
- * not outerjoin_delayed.
+ * similar. We can still consider them to match the FK.
*/
for (colno = 0; colno < fkinfo->nkeys; colno++)
{
@@ -3159,10 +2932,6 @@ match_foreign_keys_to_quals(PlannerInfo *root)
Var *leftvar;
Var *rightvar;
- /* Ignore outerjoin-delayed clauses */
- if (rinfo->outerjoin_delayed)
- continue;
-
/* Only binary OpExprs are useful for consideration */
if (!IsA(clause, OpExpr) ||
list_length(clause->args) != 2)
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 320caebd87..8674ad674d 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1044,10 +1044,11 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
/*
* If we have any RTE_RESULT relations, see if they can be deleted from
- * the jointree. This step is most effectively done after we've done
- * expression preprocessing and outer join reduction.
+ * the jointree. We also rely on this processing to flatten single-child
+ * FromExprs underneath outer joins. This step is most effectively done
+ * after we've done expression preprocessing and outer join reduction.
*/
- if (hasResultRTEs)
+ if (hasResultRTEs || hasOuterJoins)
remove_useless_result_rtes(root);
/*
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 9c96a558fc..eacfb66b31 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -130,6 +130,7 @@ static void reduce_outer_joins_pass2(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,
+ Node **parent_quals,
Relids *dropped_outer_joins);
static int get_result_relid(PlannerInfo *root, Node *jtnode);
static void remove_result_refs(PlannerInfo *root, int varno, Node *newjtloc);
@@ -3085,12 +3086,31 @@ report_reduced_full_join(reduce_outer_joins_pass2_state *state2,
/*
* remove_useless_result_rtes
* Attempt to remove RTE_RESULT RTEs from the join tree.
+ * Also, elide single-child FromExprs where possible.
*
* We can remove RTE_RESULT entries from the join tree using the knowledge
* that RTE_RESULT returns exactly one row and has no output columns. Hence,
* if one is inner-joined to anything else, we can delete it. Optimizations
* are also possible for some outer-join cases, as detailed below.
*
+ * This pass also replaces single-child FromExprs with their child node
+ * where possible. It's appropriate to do that here and not earlier because
+ * RTE_RESULT removal might reduce a multiple-child FromExpr to have only one
+ * child. We can remove such a FromExpr if its quals are empty, or if it's
+ * semantically valid to merge the quals into those of the parent node.
+ * While removing unnecessary join tree nodes has some micro-efficiency value,
+ * the real reason to do this is to eliminate cases where the nullable side of
+ * an outer join node is a FromExpr whose single child is another outer join.
+ * To correctly determine whether the two outer joins can commute,
+ * deconstruct_jointree() must treat any quals of such a FromExpr as being
+ * degenerate quals of the upper outer join. The best way to do that is to
+ * make them actually *be* quals of the upper join, by dropping the FromExpr
+ * and hoisting the quals up into the upper join's quals. (Note that there is
+ * no hazard when the intermediate FromExpr has multiple children, since then
+ * it represents an inner join that cannot commute with the upper outer join.)
+ * As long as we have to do that, we might as well elide such FromExprs
+ * everywhere.
+ *
* Some of these optimizations depend on recognizing empty (constant-true)
* quals for FromExprs and JoinExprs. That makes it useful to apply this
* optimization pass after expression preprocessing, since that will have
@@ -3131,6 +3151,7 @@ remove_useless_result_rtes(PlannerInfo *root)
root->parse->jointree = (FromExpr *)
remove_useless_results_recurse(root,
(Node *) root->parse->jointree,
+ NULL,
&dropped_outer_joins);
/* We should still have a FromExpr */
Assert(IsA(root->parse->jointree, FromExpr));
@@ -3184,9 +3205,14 @@ remove_useless_result_rtes(PlannerInfo *root)
* 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.
+ *
+ * jtnode is the current jointree node. If it could be valid to merge
+ * its quals into those of the parent node, parent_quals should point to
+ * the parent's quals list; otherwise, pass NULL for parent_quals.
*/
static Node *
remove_useless_results_recurse(PlannerInfo *root, Node *jtnode,
+ Node **parent_quals,
Relids *dropped_outer_joins)
{
Assert(jtnode != NULL);
@@ -3214,8 +3240,9 @@ remove_useless_results_recurse(PlannerInfo *root, Node *jtnode,
Node *child = (Node *) lfirst(cell);
int varno;
- /* Recursively transform child ... */
+ /* Recursively transform child, allowing it to push up quals ... */
child = remove_useless_results_recurse(root, child,
+ &f->quals,
dropped_outer_joins);
/* ... and stick it back into the tree */
lfirst(cell) = child;
@@ -3249,25 +3276,54 @@ remove_useless_results_recurse(PlannerInfo *root, Node *jtnode,
}
/*
- * If we're not at the top of the jointree, it's valid to simplify a
- * degenerate FromExpr into its single child. (At the top, we must
- * keep the FromExpr since Query.jointree is required to point to a
- * FromExpr.)
+ * If the FromExpr now has only one child, see if we can elide it.
+ * This is always valid if there are no quals, except at the top of
+ * the jointree (since Query.jointree is required to point to a
+ * FromExpr). Otherwise, we can do it if we can push the quals up to
+ * the parent node.
+ *
+ * Note: while it would not be terribly hard to generalize this
+ * transformation to merge multi-child FromExprs into their parent
+ * FromExpr, that risks making the parent join too expensive to plan.
+ * We leave it to later processing to decide heuristically whether
+ * that's a good idea. Pulling up a single child is always OK,
+ * however.
*/
- if (f != root->parse->jointree &&
- f->quals == NULL &&
- list_length(f->fromlist) == 1)
+ if (list_length(f->fromlist) == 1 &&
+ f != root->parse->jointree &&
+ (f->quals == NULL || parent_quals != NULL))
+ {
+ /*
+ * Merge any quals up to parent. They should be in implicit-AND
+ * format by now, so we just need to concatenate lists. Put the
+ * child quals at the front, on the grounds that they should
+ * nominally be evaluated earlier.
+ */
+ if (f->quals != NULL)
+ *parent_quals = (Node *)
+ list_concat(castNode(List, f->quals),
+ castNode(List, *parent_quals));
return (Node *) linitial(f->fromlist);
+ }
}
else if (IsA(jtnode, JoinExpr))
{
JoinExpr *j = (JoinExpr *) jtnode;
int varno;
- /* First, recurse */
+ /*
+ * First, recurse. We can accept pushed-up FromExpr quals from either
+ * child if the jointype is INNER, and we can accept them from the RHS
+ * child if the jointype is LEFT.
+ */
j->larg = remove_useless_results_recurse(root, j->larg,
+ (j->jointype == JOIN_INNER) ?
+ &j->quals : NULL,
dropped_outer_joins);
j->rarg = remove_useless_results_recurse(root, j->rarg,
+ (j->jointype == JOIN_INNER ||
+ j->jointype == JOIN_LEFT) ?
+ &j->quals : NULL,
dropped_outer_joins);
/* Apply join-type-specific optimization rules */
@@ -3278,9 +3334,9 @@ remove_useless_results_recurse(PlannerInfo *root, Node *jtnode,
/*
* An inner join is equivalent to a FromExpr, so if either
* side was simplified to an RTE_RESULT rel, we can replace
- * the join with a FromExpr with just the other side; and if
- * the qual is empty (JOIN ON TRUE) then we can omit the
- * FromExpr as well.
+ * the join with a FromExpr with just the other side.
+ * Furthermore, we can elide that FromExpr according to the
+ * same rules as above.
*
* Just as in the FromExpr case, we can't simplify if the
* other input rel references any PHVs that are marked as to
@@ -3295,20 +3351,34 @@ remove_useless_results_recurse(PlannerInfo *root, Node *jtnode,
!find_dependent_phvs_in_jointree(root, j->rarg, varno))
{
remove_result_refs(root, varno, j->rarg);
- if (j->quals)
+ if (j->quals != NULL && parent_quals == NULL)
jtnode = (Node *)
makeFromExpr(list_make1(j->rarg), j->quals);
else
+ {
+ /* Merge any quals up to parent */
+ if (j->quals != NULL)
+ *parent_quals = (Node *)
+ list_concat(castNode(List, j->quals),
+ castNode(List, *parent_quals));
jtnode = j->rarg;
+ }
}
else if ((varno = get_result_relid(root, j->rarg)) != 0)
{
remove_result_refs(root, varno, j->larg);
- if (j->quals)
+ if (j->quals != NULL && parent_quals == NULL)
jtnode = (Node *)
makeFromExpr(list_make1(j->larg), j->quals);
else
+ {
+ /* Merge any quals up to parent */
+ if (j->quals != NULL)
+ *parent_quals = (Node *)
+ list_concat(castNode(List, j->quals),
+ castNode(List, *parent_quals));
jtnode = j->larg;
+ }
}
break;
case JOIN_LEFT:
@@ -3346,8 +3416,9 @@ remove_useless_results_recurse(PlannerInfo *root, Node *jtnode,
/*
* We may simplify this case if the RHS is an RTE_RESULT; the
* join qual becomes effectively just a filter qual for the
- * LHS, since we should either return the LHS row or not. For
- * simplicity we inject the filter qual into a new FromExpr.
+ * LHS, since we should either return the LHS row or not. The
+ * filter clause must go into a new FromExpr if we can't push
+ * it up to the parent.
*
* There is a fine point about PHVs that are supposed to be
* evaluated at the RHS. Such PHVs could only appear in the
@@ -3365,11 +3436,18 @@ remove_useless_results_recurse(PlannerInfo *root, Node *jtnode,
{
Assert(j->rtindex == 0);
remove_result_refs(root, varno, j->larg);
- if (j->quals)
+ if (j->quals != NULL && parent_quals == NULL)
jtnode = (Node *)
makeFromExpr(list_make1(j->larg), j->quals);
else
+ {
+ /* Merge any quals up to parent */
+ if (j->quals != NULL)
+ *parent_quals = (Node *)
+ list_concat(castNode(List, j->quals),
+ castNode(List, *parent_quals));
jtnode = j->larg;
+ }
}
break;
case JOIN_FULL:
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d449b5c274..9d377385f1 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -467,9 +467,6 @@ adjust_appendrel_attrs_mutator(Node *node,
newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids,
context->nappinfos,
context->appinfos);
- newinfo->nullable_relids = adjust_child_relids(oldinfo->nullable_relids,
- context->nappinfos,
- context->appinfos);
newinfo->left_relids = adjust_child_relids(oldinfo->left_relids,
context->nappinfos,
context->appinfos);
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index bf3ea26cf4..bae9688e46 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -894,10 +894,9 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
make_restrictinfo(root,
(Expr *) onecq,
rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
pseudoconstant,
rinfo->security_level,
- NULL, NULL, NULL));
+ NULL, NULL));
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
@@ -930,9 +929,9 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
/* not likely that we'd see constants here, so no check */
childquals = lappend(childquals,
make_restrictinfo(root, qual,
- true, false, false,
+ true, false,
security_level,
- NULL, NULL, NULL));
+ NULL, NULL));
cq_min_security = Min(cq_min_security, security_level);
}
security_level++;
diff --git a/src/backend/optimizer/util/orclauses.c b/src/backend/optimizer/util/orclauses.c
index abc994dbf2..85ecdfc14f 100644
--- a/src/backend/optimizer/util/orclauses.c
+++ b/src/backend/optimizer/util/orclauses.c
@@ -98,18 +98,13 @@ extract_restriction_or_clauses(PlannerInfo *root)
* joinclause that is considered safe to move to this rel by the
* parameterized-path machinery, even though what we are going to do
* with it is not exactly a parameterized path.
- *
- * However, it seems best to ignore clauses that have been marked
- * redundant (by setting norm_selec > 1). That likely can't happen
- * for OR clauses, but let's be safe.
*/
foreach(lc, rel->joininfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
if (restriction_is_or_clause(rinfo) &&
- join_clause_is_movable_to(rinfo, rel) &&
- rinfo->norm_selec <= 1)
+ join_clause_is_movable_to(rinfo, rel))
{
/* Try to extract a qual for this rel only */
Expr *orclause = extract_or_clause(rinfo, rel);
@@ -272,10 +267,8 @@ consider_new_or_clause(PlannerInfo *root, RelOptInfo *rel,
orclause,
true,
false,
- false,
join_or_rinfo->security_level,
NULL,
- NULL,
NULL);
/*
@@ -344,7 +337,6 @@ consider_new_or_clause(PlannerInfo *root, RelOptInfo *rel,
sjinfo.commute_below = NULL;
/* we don't bother trying to make the remaining fields valid */
sjinfo.lhs_strict = false;
- sjinfo.delay_upper_joins = false;
sjinfo.semi_can_btree = false;
sjinfo.semi_can_hash = false;
sjinfo.semi_operators = NIL;
@@ -356,7 +348,7 @@ consider_new_or_clause(PlannerInfo *root, RelOptInfo *rel,
/* And hack cached selectivity so join size remains the same */
join_or_rinfo->norm_selec = orig_selec / or_selec;
- /* ensure result stays in sane range, in particular not "redundant" */
+ /* ensure result stays in sane range */
if (join_or_rinfo->norm_selec > 1)
join_or_rinfo->norm_selec = 1;
/* as explained above, we don't touch outer_selec */
diff --git a/src/backend/optimizer/util/placeholder.c b/src/backend/optimizer/util/placeholder.c
index af10dbd124..9c6cb5eba7 100644
--- a/src/backend/optimizer/util/placeholder.c
+++ b/src/backend/optimizer/util/placeholder.c
@@ -134,7 +134,6 @@ find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv)
phinfo->ph_eval_at = bms_copy(phv->phrels);
Assert(!bms_is_empty(phinfo->ph_eval_at));
}
- /* ph_eval_at may change later, see update_placeholder_eval_levels */
phinfo->ph_needed = NULL; /* initially it's unused */
/* for the moment, estimate width using just the datatype info */
phinfo->ph_width = get_typavgwidth(exprType((Node *) phv->phexpr),
@@ -285,102 +284,6 @@ find_placeholders_in_expr(PlannerInfo *root, Node *expr)
}
/*
- * update_placeholder_eval_levels
- * Adjust the target evaluation levels for placeholders
- *
- * The initial eval_at level set by find_placeholder_info was the set of
- * rels used in the placeholder's expression (or the whole subselect below
- * the placeholder's syntactic location, if the expr is variable-free).
- * If the query contains any outer joins that can null any of those rels,
- * we must delay evaluation to above those joins.
- *
- * We repeat this operation each time we add another outer join to
- * root->join_info_list. It's somewhat annoying to have to do that, but
- * since we don't have very much information on the placeholders' locations,
- * it's hard to avoid. Each placeholder's eval_at level must be correct
- * by the time it starts to figure in outer-join delay decisions for higher
- * outer joins.
- *
- * In future we might want to put additional policy/heuristics here to
- * try to determine an optimal evaluation level. The current rules will
- * result in evaluation at the lowest possible level. However, pushing a
- * placeholder eval up the tree is likely to further constrain evaluation
- * order for outer joins, so it could easily be counterproductive; and we
- * don't have enough information at this point to make an intelligent choice.
- */
-void
-update_placeholder_eval_levels(PlannerInfo *root, SpecialJoinInfo *new_sjinfo)
-{
- ListCell *lc1;
-
- foreach(lc1, root->placeholder_list)
- {
- PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc1);
- Relids syn_level = phinfo->ph_var->phrels;
- Relids eval_at;
- bool found_some;
- ListCell *lc2;
-
- /*
- * We don't need to do any work on this placeholder unless the
- * newly-added outer join is syntactically beneath its location.
- */
- if (!bms_is_subset(new_sjinfo->syn_lefthand, syn_level) ||
- !bms_is_subset(new_sjinfo->syn_righthand, syn_level))
- continue;
-
- /*
- * Check for delays due to lower outer joins. This is the same logic
- * as in check_outerjoin_delay in initsplan.c, except that we don't
- * have anything to do with the delay_upper_joins flags; delay of
- * upper outer joins will be handled later, based on the eval_at
- * values we compute now.
- */
- eval_at = phinfo->ph_eval_at;
-
- do
- {
- found_some = false;
- foreach(lc2, root->join_info_list)
- {
- SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc2);
-
- /* disregard joins not within the PHV's sub-select */
- if (!bms_is_subset(sjinfo->syn_lefthand, syn_level) ||
- !bms_is_subset(sjinfo->syn_righthand, syn_level))
- continue;
-
- /* do we reference any nullable rels of this OJ? */
- if (bms_overlap(eval_at, sjinfo->min_righthand) ||
- (sjinfo->jointype == JOIN_FULL &&
- bms_overlap(eval_at, sjinfo->min_lefthand)))
- {
- /* yes; have we included all its rels in eval_at? */
- if (!bms_is_subset(sjinfo->min_lefthand, eval_at) ||
- !bms_is_subset(sjinfo->min_righthand, eval_at))
- {
- /* no, so add them in */
- eval_at = bms_add_members(eval_at,
- 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;
- }
- }
- }
- } while (found_some);
-
- /* Can't move the PHV's eval_at level to above its syntactic level */
- Assert(bms_is_subset(eval_at, syn_level));
-
- phinfo->ph_eval_at = eval_at;
- }
-}
-
-/*
* fix_placeholder_input_needed_levels
* Adjust the "needed at" levels for placeholder inputs
*
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index ebfb4ddd12..ad84cc43e1 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -284,6 +284,12 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
rel->top_parent_relids = rel->top_parent->relids;
/*
+ * A child rel is below the same outer joins as its parent. (We
+ * presume this info was already calculated for the parent.)
+ */
+ rel->nulling_relids = parent->nulling_relids;
+
+ /*
* Also propagate lateral-reference information from appendrel parent
* rels to their child rels. We intentionally give each child rel the
* same minimum parameterization, even though it's quite possible that
@@ -306,6 +312,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
rel->parent = NULL;
rel->top_parent = NULL;
rel->top_parent_relids = NULL;
+ rel->nulling_relids = NULL;
rel->direct_lateral_relids = NULL;
rel->lateral_relids = NULL;
rel->lateral_referencers = NULL;
@@ -685,6 +692,7 @@ build_join_rel(PlannerInfo *root,
joinrel->max_attr = 0;
joinrel->attr_needed = NULL;
joinrel->attr_widths = NULL;
+ joinrel->nulling_relids = NULL;
joinrel->lateral_vars = NIL;
joinrel->lateral_referencers = NULL;
joinrel->indexlist = NIL;
@@ -874,6 +882,7 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
joinrel->max_attr = 0;
joinrel->attr_needed = NULL;
joinrel->attr_widths = NULL;
+ joinrel->nulling_relids = NULL;
joinrel->lateral_vars = NIL;
joinrel->lateral_referencers = NULL;
joinrel->indexlist = NIL;
@@ -1646,18 +1655,9 @@ get_joinrel_parampathinfo(PlannerInfo *root, RelOptInfo *joinrel,
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
- /*
- * In principle, join_clause_is_movable_into() should accept anything
- * returned by generate_join_implied_equalities(); but because its
- * analysis is only approximate, sometimes it doesn't. So we
- * currently cannot use this Assert; instead just assume it's okay to
- * apply the joinclause at this level.
- */
-#ifdef NOT_USED
Assert(join_clause_is_movable_into(rinfo,
joinrel->relids,
join_and_req));
-#endif
if (join_clause_is_movable_into(rinfo,
outer_path->parent->relids,
outer_and_req))
@@ -1720,12 +1720,9 @@ get_joinrel_parampathinfo(PlannerInfo *root, RelOptInfo *joinrel,
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
- /* As above, can't quite assert this here */
-#ifdef NOT_USED
Assert(join_clause_is_movable_into(rinfo,
outer_path->parent->relids,
real_outer_and_req));
-#endif
if (!join_clause_is_movable_into(rinfo,
outer_path->parent->relids,
outer_and_req))
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index 1350f011a6..c44bd2f815 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -25,21 +25,17 @@ static RestrictInfo *make_restrictinfo_internal(PlannerInfo *root,
Expr *clause,
Expr *orclause,
bool is_pushed_down,
- bool outerjoin_delayed,
bool pseudoconstant,
Index security_level,
Relids required_relids,
- Relids outer_relids,
- Relids nullable_relids);
+ Relids outer_relids);
static Expr *make_sub_restrictinfos(PlannerInfo *root,
Expr *clause,
bool is_pushed_down,
- bool outerjoin_delayed,
bool pseudoconstant,
Index security_level,
Relids required_relids,
- Relids outer_relids,
- Relids nullable_relids);
+ Relids outer_relids);
/*
@@ -47,9 +43,9 @@ static Expr *make_sub_restrictinfos(PlannerInfo *root,
*
* Build a RestrictInfo node containing the given subexpression.
*
- * The is_pushed_down, outerjoin_delayed, and pseudoconstant flags for the
+ * The is_pushed_down and pseudoconstant flags for the
* RestrictInfo must be supplied by the caller, as well as the correct values
- * for security_level, outer_relids, and nullable_relids.
+ * for security_level and outer_relids.
* required_relids can be NULL, in which case it defaults to the actual clause
* contents (i.e., clause_relids).
*
@@ -65,12 +61,10 @@ RestrictInfo *
make_restrictinfo(PlannerInfo *root,
Expr *clause,
bool is_pushed_down,
- bool outerjoin_delayed,
bool pseudoconstant,
Index security_level,
Relids required_relids,
- Relids outer_relids,
- Relids nullable_relids)
+ Relids outer_relids)
{
/*
* If it's an OR clause, build a modified copy with RestrictInfos inserted
@@ -80,12 +74,10 @@ make_restrictinfo(PlannerInfo *root,
return (RestrictInfo *) make_sub_restrictinfos(root,
clause,
is_pushed_down,
- outerjoin_delayed,
pseudoconstant,
security_level,
required_relids,
- outer_relids,
- nullable_relids);
+ outer_relids);
/* Shouldn't be an AND clause, else AND/OR flattening messed up */
Assert(!is_andclause(clause));
@@ -94,12 +86,10 @@ make_restrictinfo(PlannerInfo *root,
clause,
NULL,
is_pushed_down,
- outerjoin_delayed,
pseudoconstant,
security_level,
required_relids,
- outer_relids,
- nullable_relids);
+ outer_relids);
}
/*
@@ -112,12 +102,10 @@ make_restrictinfo_internal(PlannerInfo *root,
Expr *clause,
Expr *orclause,
bool is_pushed_down,
- bool outerjoin_delayed,
bool pseudoconstant,
Index security_level,
Relids required_relids,
- Relids outer_relids,
- Relids nullable_relids)
+ Relids outer_relids)
{
RestrictInfo *restrictinfo = makeNode(RestrictInfo);
Relids baserels;
@@ -125,14 +113,12 @@ make_restrictinfo_internal(PlannerInfo *root,
restrictinfo->clause = clause;
restrictinfo->orclause = orclause;
restrictinfo->is_pushed_down = is_pushed_down;
- restrictinfo->outerjoin_delayed = outerjoin_delayed;
restrictinfo->pseudoconstant = pseudoconstant;
restrictinfo->has_clone = false; /* may get set by caller */
restrictinfo->is_clone = false; /* may get set by caller */
restrictinfo->can_join = false; /* may get set below */
restrictinfo->security_level = security_level;
restrictinfo->outer_relids = outer_relids;
- restrictinfo->nullable_relids = nullable_relids;
/*
* If it's potentially delayable by lower-level security quals, figure out
@@ -258,9 +244,9 @@ make_restrictinfo_internal(PlannerInfo *root,
* implicit-AND lists at top level of RestrictInfo lists. Only ORs and
* simple clauses are valid RestrictInfos.
*
- * The same is_pushed_down, outerjoin_delayed, and pseudoconstant flag
+ * The same is_pushed_down and pseudoconstant flag
* values can be applied to all RestrictInfo nodes in the result. Likewise
- * for security_level, outer_relids, and nullable_relids.
+ * for security_level and outer_relids.
*
* The given required_relids are attached to our top-level output,
* but any OR-clause constituents are allowed to default to just the
@@ -270,12 +256,10 @@ static Expr *
make_sub_restrictinfos(PlannerInfo *root,
Expr *clause,
bool is_pushed_down,
- bool outerjoin_delayed,
bool pseudoconstant,
Index security_level,
Relids required_relids,
- Relids outer_relids,
- Relids nullable_relids)
+ Relids outer_relids)
{
if (is_orclause(clause))
{
@@ -287,22 +271,18 @@ make_sub_restrictinfos(PlannerInfo *root,
make_sub_restrictinfos(root,
lfirst(temp),
is_pushed_down,
- outerjoin_delayed,
pseudoconstant,
security_level,
NULL,
- outer_relids,
- nullable_relids));
+ outer_relids));
return (Expr *) make_restrictinfo_internal(root,
clause,
make_orclause(orlist),
is_pushed_down,
- outerjoin_delayed,
pseudoconstant,
security_level,
required_relids,
- outer_relids,
- nullable_relids);
+ outer_relids);
}
else if (is_andclause(clause))
{
@@ -314,12 +294,10 @@ make_sub_restrictinfos(PlannerInfo *root,
make_sub_restrictinfos(root,
lfirst(temp),
is_pushed_down,
- outerjoin_delayed,
pseudoconstant,
security_level,
required_relids,
- outer_relids,
- nullable_relids));
+ outer_relids));
return make_andclause(andlist);
}
else
@@ -327,12 +305,10 @@ make_sub_restrictinfos(PlannerInfo *root,
clause,
NULL,
is_pushed_down,
- outerjoin_delayed,
pseudoconstant,
security_level,
required_relids,
- outer_relids,
- nullable_relids);
+ outer_relids);
}
/*
@@ -437,6 +413,21 @@ restriction_is_securely_promotable(RestrictInfo *restrictinfo,
}
/*
+ * Detect whether a RestrictInfo's clause is constant TRUE (note that it's
+ * surely of type boolean). No such WHERE clause could survive qual
+ * canonicalization, but equivclass.c may generate such RestrictInfos for
+ * reasons discussed therein. We should drop them again when creating
+ * the finished plan, which is handled by the next few functions.
+ */
+static inline bool
+rinfo_is_constant_true(RestrictInfo *rinfo)
+{
+ return IsA(rinfo->clause, Const) &&
+ !((Const *) rinfo->clause)->constisnull &&
+ DatumGetBool(((Const *) rinfo->clause)->constvalue);
+}
+
+/*
* get_actual_clauses
*
* Returns a list containing the bare clauses from 'restrictinfo_list'.
@@ -455,6 +446,7 @@ get_actual_clauses(List *restrictinfo_list)
RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
Assert(!rinfo->pseudoconstant);
+ Assert(!rinfo_is_constant_true(rinfo));
result = lappend(result, rinfo->clause);
}
@@ -466,6 +458,7 @@ get_actual_clauses(List *restrictinfo_list)
*
* Extract bare clauses from 'restrictinfo_list', returning either the
* regular ones or the pseudoconstant ones per 'pseudoconstant'.
+ * Constant-TRUE clauses are dropped in any case.
*/
List *
extract_actual_clauses(List *restrictinfo_list,
@@ -478,7 +471,8 @@ extract_actual_clauses(List *restrictinfo_list,
{
RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
- if (rinfo->pseudoconstant == pseudoconstant)
+ if (rinfo->pseudoconstant == pseudoconstant &&
+ !rinfo_is_constant_true(rinfo))
result = lappend(result, rinfo->clause);
}
return result;
@@ -489,7 +483,7 @@ extract_actual_clauses(List *restrictinfo_list,
*
* Extract bare clauses from 'restrictinfo_list', separating those that
* semantically match the join level from those that were pushed down.
- * Pseudoconstant clauses are excluded from the results.
+ * Pseudoconstant and constant-TRUE clauses are excluded from the results.
*
* This is only used at outer joins, since for plain joins we don't care
* about pushed-down-ness.
@@ -511,13 +505,15 @@ extract_actual_join_clauses(List *restrictinfo_list,
if (RINFO_IS_PUSHED_DOWN(rinfo, joinrelids))
{
- if (!rinfo->pseudoconstant)
+ if (!rinfo->pseudoconstant &&
+ !rinfo_is_constant_true(rinfo))
*otherquals = lappend(*otherquals, rinfo->clause);
}
else
{
/* joinquals shouldn't have been marked pseudoconstant */
Assert(!rinfo->pseudoconstant);
+ Assert(!rinfo_is_constant_true(rinfo));
*joinquals = lappend(*joinquals, rinfo->clause);
}
}
@@ -618,8 +614,17 @@ join_clause_is_movable_to(RestrictInfo *rinfo, RelOptInfo *baserel)
if (bms_is_member(baserel->relid, rinfo->outer_relids))
return false;
- /* Target rel must not be nullable below the clause */
- if (bms_is_member(baserel->relid, rinfo->nullable_relids))
+ /*
+ * Target rel's Vars must not be nulled by any outer join. We can check
+ * this without groveling through the individual Vars by seeing whether
+ * clause_relids (which includes all such Vars' varnullingrels) includes
+ * any outer join that can null the target rel. You might object that
+ * this could reject the clause on the basis of an OJ relid that came from
+ * some other rel's Var. However, that would still mean that the clause
+ * came from above that outer join and shouldn't be pushed down; so there
+ * should be no false positives.
+ */
+ if (bms_overlap(rinfo->clause_relids, baserel->nulling_relids))
return false;
/* Clause must not use any rels with LATERAL references to this rel */
@@ -651,18 +656,15 @@ join_clause_is_movable_to(RestrictInfo *rinfo, RelOptInfo *baserel)
* relation plus the outer rels. We also check that it does reference at
* least one current Var, ensuring that the clause will be pushed down to
* a unique place in a parameterized join tree. And we check that we're
- * not pushing the clause into its outer-join outer side, nor down into
- * a lower outer join's inner side.
- *
- * The check about pushing a clause down into a lower outer join's inner side
- * is only approximate; it sometimes returns "false" when actually it would
- * be safe to use the clause here because we're still above the outer join
- * in question. This is okay as long as the answers at different join levels
- * are consistent: it just means we might sometimes fail to push a clause as
- * far down as it could safely be pushed. It's unclear whether it would be
- * worthwhile to do this more precisely. (But if it's ever fixed to be
- * exactly accurate, there's an Assert in get_joinrel_parampathinfo() that
- * should be re-enabled.)
+ * not pushing the clause into its outer-join outer side.
+ *
+ * We used to need to check that we're not pushing the clause into a lower
+ * outer join's inner side. However, now that clause_relids includes
+ * references to potentially-nulling outer joins, the other tests handle that
+ * concern. If the clause references any Var coming from the inside of a
+ * lower outer join, its clause_relids will mention that outer join, causing
+ * the evaluability check to fail; while if it references no such Vars, the
+ * references-a-target-rel check will fail.
*
* There's no check here equivalent to join_clause_is_movable_to's test on
* lateral_referencers. We assume the caller wouldn't be inquiring unless
@@ -704,14 +706,5 @@ join_clause_is_movable_into(RestrictInfo *rinfo,
if (bms_overlap(currentrelids, rinfo->outer_relids))
return false;
- /*
- * Target rel(s) must not be nullable below the clause. This is
- * approximate, in the safe direction, because the current join might be
- * above the join where the nulling would happen, in which case the clause
- * would work correctly here. But we don't have enough info to be sure.
- */
- if (bms_overlap(currentrelids, rinfo->nullable_relids))
- return false;
-
return true;
}