summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--contrib/postgres_fdw/postgres_fdw.c2
-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
-rw-r--r--src/include/nodes/pathnodes.h48
-rw-r--r--src/include/optimizer/paths.h3
-rw-r--r--src/include/optimizer/placeholder.h2
-rw-r--r--src/include/optimizer/planmain.h2
-rw-r--r--src/include/optimizer/restrictinfo.h6
-rw-r--r--src/test/regress/expected/join.out34
-rw-r--r--src/test/regress/sql/join.sql4
24 files changed, 325 insertions, 763 deletions
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 29b2be602c..f5926ab89d 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -6522,10 +6522,8 @@ foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel,
expr,
true,
false,
- false,
root->qual_security_level,
grouped_rel->relids,
- NULL,
NULL);
if (is_foreign_expr(root, grouped_rel, expr))
fpinfo->remote_conds = lappend(fpinfo->remote_conds, rinfo);
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;
}
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 630a044089..f843659a18 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -269,14 +269,6 @@ struct PlannerInfo
Relids all_query_rels;
/*
- * nullable_baserels is a Relids set of base relids that are nullable by
- * some outer join in the jointree; these are rels that are potentially
- * nullable below the WHERE clause, SELECT targetlist, etc. This is
- * computed in deconstruct_jointree.
- */
- Relids nullable_baserels;
-
- /*
* join_rel_list is a list of all join-relation RelOptInfos we have
* considered in this planning run. For small problems we just scan the
* list to do lookups, but when there are many join relations we build a
@@ -694,6 +686,7 @@ typedef struct PartitionSchemeData *PartitionScheme;
* the attribute is needed as part of final targetlist
* attr_widths - cache space for per-attribute width estimates;
* zero means not computed yet
+ * nulling_relids - relids of outer joins that can null this rel
* lateral_vars - lateral cross-references of rel, if any (list of
* Vars and PlaceHolderVars)
* lateral_referencers - relids of rels that reference this one laterally
@@ -927,6 +920,8 @@ typedef struct RelOptInfo
Relids *attr_needed pg_node_attr(read_write_ignore);
/* array indexed [min_attr .. max_attr] */
int32 *attr_widths pg_node_attr(read_write_ignore);
+ /* relids of outer joins that can null this baserel */
+ Relids nulling_relids;
/* LATERAL Vars and PHVs referenced by rel */
List *lateral_vars;
/* rels that reference this baserel laterally */
@@ -1389,7 +1384,6 @@ typedef struct EquivalenceMember
Expr *em_expr; /* the expression represented */
Relids em_relids; /* all relids appearing in em_expr */
- Relids em_nullable_relids; /* nullable by lower outer joins */
bool em_is_const; /* expression is pseudoconstant? */
bool em_is_child; /* derived version for a child relation? */
Oid em_datatype; /* the "nominal type" used by the opfamily */
@@ -2404,26 +2398,12 @@ typedef struct LimitPath
* conditions. Possibly we should rename it to reflect that meaning? But
* see also the comments for RINFO_IS_PUSHED_DOWN, below.)
*
- * RestrictInfo nodes also contain an outerjoin_delayed flag, which is true
- * if the clause's applicability must be delayed due to any outer joins
- * appearing below it (ie, it has to be postponed to some join level higher
- * than the set of relations it actually references).
- *
* There is also an outer_relids field, which is NULL except for outer join
* clauses; for those, it is the set of relids on the outer side of the
* clause's outer join. (These are rels that the clause cannot be applied to
* in parameterized scans, since pushing it into the join's outer side would
* lead to wrong answers.)
*
- * There is also a nullable_relids field, which is the set of rels the clause
- * references that can be forced null by some outer join below the clause.
- *
- * outerjoin_delayed = true is subtly different from nullable_relids != NULL:
- * a clause might reference some nullable rels and yet not be
- * outerjoin_delayed because it also references all the other rels of the
- * outer join(s). A clause that is not outerjoin_delayed can be enforced
- * anywhere it is computable.
- *
* To handle security-barrier conditions efficiently, we mark RestrictInfo
* nodes with a security_level field, in which higher values identify clauses
* coming from less-trusted sources. The exact semantics are that a clause
@@ -2497,9 +2477,6 @@ typedef struct RestrictInfo
/* true if clause was pushed down in level */
bool is_pushed_down;
- /* true if delayed by lower outer join */
- bool outerjoin_delayed;
-
/* see comment above */
bool can_join pg_node_attr(equal_ignore);
@@ -2531,9 +2508,6 @@ typedef struct RestrictInfo
/* If an outer-join clause, the outer-side relations, else NULL: */
Relids outer_relids;
- /* The relids used in the clause that are nullable by lower outer joins: */
- Relids nullable_relids;
-
/*
* Relids in the left/right side of the clause. These fields are set for
* any binary opclause.
@@ -2579,10 +2553,7 @@ typedef struct RestrictInfo
/* eval cost of clause; -1 if not yet set */
QualCost eval_cost pg_node_attr(equal_ignore);
- /*
- * selectivity for "normal" (JOIN_INNER) semantics; -1 if not yet set; >1
- * means a redundant clause
- */
+ /* selectivity for "normal" (JOIN_INNER) semantics; -1 if not yet set */
Selectivity norm_selec pg_node_attr(equal_ignore);
/* selectivity for outer join semantics; -1 if not yet set */
Selectivity outer_selec pg_node_attr(equal_ignore);
@@ -2788,12 +2759,6 @@ typedef struct PlaceHolderVar
* upper-level outer joins even if it appears in their RHS). We don't bother
* to set lhs_strict for FULL JOINs, however.
*
- * delay_upper_joins is set true if we detect a pushed-down clause that has
- * to be evaluated after this join is formed (because it references the RHS).
- * Any outer joins that have such a clause and this join in their RHS cannot
- * commute with this join, because that would leave noplace to check the
- * pushed-down clause. (We don't track this for FULL JOINs, either.)
- *
* For a semijoin, we also extract the join operators and their RHS arguments
* and set semi_operators, semi_rhs_exprs, semi_can_btree, and semi_can_hash.
* This is done in support of possibly unique-ifying the RHS, so we don't
@@ -2808,8 +2773,8 @@ typedef struct PlaceHolderVar
* not allowed within join_info_list. We also create transient
* SpecialJoinInfos with jointype == JOIN_INNER for outer joins, since for
* cost estimation purposes it is sometimes useful to know the join size under
- * plain innerjoin semantics. Note that lhs_strict, delay_upper_joins, and
- * of course the semi_xxx fields are not set meaningfully within such structs.
+ * plain innerjoin semantics. Note that lhs_strict and the semi_xxx fields
+ * are not set meaningfully within such structs.
*/
#ifndef HAVE_SPECIALJOININFO_TYPEDEF
typedef struct SpecialJoinInfo SpecialJoinInfo;
@@ -2831,7 +2796,6 @@ struct SpecialJoinInfo
Relids commute_above_r; /* commuting OJs above this one, if RHS */
Relids commute_below; /* commuting OJs below this one */
bool lhs_strict; /* joinclause is strict for some LHS rel */
- bool delay_upper_joins; /* can't commute with upper RHS */
/* Remaining fields are set only for JOIN_SEMI jointype: */
bool semi_can_btree; /* true if semi_operators are all btree */
bool semi_can_hash; /* true if semi_operators are all hash */
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 9b38627efd..1b02a1dc08 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -128,7 +128,6 @@ extern Expr *canonicalize_ec_expression(Expr *expr,
extern void reconsider_outer_join_clauses(PlannerInfo *root);
extern EquivalenceClass *get_eclass_for_sort_expr(PlannerInfo *root,
Expr *expr,
- Relids nullable_relids,
List *opfamilies,
Oid opcintype,
Oid collation,
@@ -216,7 +215,7 @@ extern List *build_index_pathkeys(PlannerInfo *root, IndexOptInfo *index,
extern List *build_partition_pathkeys(PlannerInfo *root, RelOptInfo *partrel,
ScanDirection scandir, bool *partialkeys);
extern List *build_expression_pathkey(PlannerInfo *root, Expr *expr,
- Relids nullable_relids, Oid opno,
+ Oid opno,
Relids rel, bool create_it);
extern List *convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
List *subquery_pathkeys,
diff --git a/src/include/optimizer/placeholder.h b/src/include/optimizer/placeholder.h
index 31e1578e82..acb9cf9f05 100644
--- a/src/include/optimizer/placeholder.h
+++ b/src/include/optimizer/placeholder.h
@@ -22,8 +22,6 @@ extern PlaceHolderVar *make_placeholder_expr(PlannerInfo *root, Expr *expr,
extern PlaceHolderInfo *find_placeholder_info(PlannerInfo *root,
PlaceHolderVar *phv);
extern void find_placeholders_in_jointree(PlannerInfo *root);
-extern void update_placeholder_eval_levels(PlannerInfo *root,
- SpecialJoinInfo *new_sjinfo);
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,
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 95ecefdade..3e6e60f549 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -83,7 +83,6 @@ extern RestrictInfo *process_implied_equality(PlannerInfo *root,
Expr *item1,
Expr *item2,
Relids qualscope,
- Relids nullable_relids,
Index security_level,
bool below_outer_join,
bool both_const);
@@ -93,7 +92,6 @@ extern RestrictInfo *build_implied_join_equality(PlannerInfo *root,
Expr *item1,
Expr *item2,
Relids qualscope,
- Relids nullable_relids,
Index security_level);
extern void match_foreign_keys_to_quals(PlannerInfo *root);
diff --git a/src/include/optimizer/restrictinfo.h b/src/include/optimizer/restrictinfo.h
index c79bb420e4..c9e30776c5 100644
--- a/src/include/optimizer/restrictinfo.h
+++ b/src/include/optimizer/restrictinfo.h
@@ -19,17 +19,15 @@
/* Convenience macro for the common case of a valid-everywhere qual */
#define make_simple_restrictinfo(root, clause) \
- make_restrictinfo(root, clause, true, false, false, 0, NULL, NULL, NULL)
+ make_restrictinfo(root, clause, true, false, 0, NULL, NULL)
extern 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);
extern RestrictInfo *commute_restrictinfo(RestrictInfo *rinfo, Oid comm_op);
extern bool restriction_is_or_clause(RestrictInfo *restrictinfo);
extern bool restriction_is_securely_promotable(RestrictInfo *restrictinfo,
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index 51c9df3d58..87e1276804 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -2308,8 +2308,8 @@ order by 1, 2;
(5 rows)
--
--- regression test: check a case where join_clause_is_movable_into() gives
--- an imprecise result, causing an assertion failure
+-- regression test: check a case where join_clause_is_movable_into()
+-- used to give an imprecise result, causing an assertion failure
--
select count(*)
from
@@ -4020,10 +4020,10 @@ explain (costs off)
select q1, unique2, thousand, hundred
from int8_tbl a left join tenk1 b on q1 = unique2
where coalesce(thousand,123) = q1 and q1 = coalesce(hundred,123);
- QUERY PLAN
---------------------------------------------------------------------------------------
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------
Nested Loop Left Join
- Filter: ((COALESCE(b.thousand, 123) = a.q1) AND (a.q1 = COALESCE(b.hundred, 123)))
+ Filter: ((COALESCE(b.thousand, 123) = COALESCE(b.hundred, 123)) AND (a.q1 = COALESCE(b.hundred, 123)))
-> Seq Scan on int8_tbl a
-> Index Scan using tenk1_unique2 on tenk1 b
Index Cond: (unique2 = a.q1)
@@ -4064,8 +4064,8 @@ explain (costs off)
select a.unique1, b.unique1, c.unique1, coalesce(b.twothousand, a.twothousand)
from tenk1 a left join tenk1 b on b.thousand = a.unique1 left join tenk1 c on c.unique2 = coalesce(b.twothousand, a.twothousand)
where a.unique2 < 10 and coalesce(b.twothousand, a.twothousand) = 44;
- QUERY PLAN
----------------------------------------------------------------------------------------------
+ QUERY PLAN
+---------------------------------------------------------------
Nested Loop Left Join
-> Nested Loop Left Join
Filter: (COALESCE(b.twothousand, a.twothousand) = 44)
@@ -4076,7 +4076,7 @@ select a.unique1, b.unique1, c.unique1, coalesce(b.twothousand, a.twothousand)
-> Bitmap Index Scan on tenk1_thous_tenthous
Index Cond: (thousand = a.unique1)
-> Index Scan using tenk1_unique2 on tenk1 c
- Index Cond: ((unique2 = COALESCE(b.twothousand, a.twothousand)) AND (unique2 = 44))
+ Index Cond: (unique2 = 44)
(11 rows)
select a.unique1, b.unique1, c.unique1, coalesce(b.twothousand, a.twothousand)
@@ -4561,7 +4561,6 @@ where tt1.f1 = ss1.c0;
Output: tt4.f1
-> Nested Loop Left Join
Output: tt4.f1
- Join Filter: (tt3.f1 = tt4.f1)
-> Seq Scan on public.text_tbl tt3
Output: tt3.f1
Filter: (tt3.f1 = 'foo'::text)
@@ -4579,7 +4578,7 @@ where tt1.f1 = ss1.c0;
Output: (tt4.f1)
-> Seq Scan on public.text_tbl tt5
Output: tt4.f1
-(33 rows)
+(32 rows)
select 1 from
text_tbl as tt1
@@ -4686,24 +4685,22 @@ explain (costs off)
QUERY PLAN
-------------------------------------------------
Nested Loop Left Join
- Join Filter: (a.f1 = b.unique2)
-> Seq Scan on int4_tbl a
Filter: (f1 = 0)
-> Index Scan using tenk1_unique2 on tenk1 b
Index Cond: (unique2 = 0)
-(6 rows)
+(5 rows)
explain (costs off)
select * from tenk1 a full join tenk1 b using(unique2) where unique2 = 42;
QUERY PLAN
-------------------------------------------------
Merge Full Join
- Merge Cond: (a.unique2 = b.unique2)
-> Index Scan using tenk1_unique2 on tenk1 a
Index Cond: (unique2 = 42)
-> Index Scan using tenk1_unique2 on tenk1 b
Index Cond: (unique2 = 42)
-(6 rows)
+(5 rows)
--
-- test that quals attached to an outer join have correct semantics,
@@ -4791,12 +4788,11 @@ select a.unique1, b.unique2
QUERY PLAN
----------------------------------------------------
Nested Loop Left Join
- Join Filter: (a.unique1 = b.unique2)
-> Index Only Scan using onek_unique1 on onek a
Index Cond: (unique1 = 42)
-> Index Only Scan using onek_unique2 on onek b
Index Cond: (unique2 = 42)
-(6 rows)
+(5 rows)
select a.unique1, b.unique2
from onek a full join onek b on a.unique1 = b.unique2
@@ -4813,12 +4809,11 @@ select a.unique1, b.unique2
QUERY PLAN
----------------------------------------------------
Nested Loop Left Join
- Join Filter: (a.unique1 = b.unique2)
-> Index Only Scan using onek_unique2 on onek b
Index Cond: (unique2 = 43)
-> Index Only Scan using onek_unique1 on onek a
Index Cond: (unique1 = 43)
-(6 rows)
+(5 rows)
select a.unique1, b.unique2
from onek a full join onek b on a.unique1 = b.unique2
@@ -6202,12 +6197,13 @@ select * from int8_tbl i8 left join lateral
--------------------------------------
Nested Loop Left Join
Output: i8.q1, i8.q2, f1, (i8.q2)
+ Join Filter: false
-> Seq Scan on public.int8_tbl i8
Output: i8.q1, i8.q2
-> Result
Output: f1, i8.q2
One-Time Filter: false
-(7 rows)
+(8 rows)
explain (verbose, costs off)
select * from int8_tbl i8 left join lateral
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index 7035e4a4c4..2bb24dbfcd 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -411,8 +411,8 @@ select * from int8_tbl i1 left join (int8_tbl i2 join
order by 1, 2;
--
--- regression test: check a case where join_clause_is_movable_into() gives
--- an imprecise result, causing an assertion failure
+-- regression test: check a case where join_clause_is_movable_into()
+-- used to give an imprecise result, causing an assertion failure
--
select count(*)
from