Do assorted mop-up in the planner.

Remove RestrictInfo.nullable_relids, along with a good deal of
infrastructure that calculated it.  One use-case for it was in
join_clause_is_movable_to, but we can now replace that usage with
a check to see if the clause's relids include any outer join
that can null the target relation.  The other use-case was in
join_clause_is_movable_into, but that test can just be dropped
entirely now that the clause's relids include outer joins.
Furthermore, join_clause_is_movable_into should now be
accurate enough that it will accept anything returned by
generate_join_implied_equalities, so we can restore the Assert
that was diked out in commit 95f4e59c3.

Remove the outerjoin_delayed mechanism.  We needed this before to
prevent quals from getting evaluated below outer joins that should
null some of their vars.  Now that we consider varnullingrels while
placing quals, that's taken care of automatically, so throw the
whole thing away.

Teach remove_useless_result_rtes to also remove useless FromExprs.
Having done that, the delay_upper_joins flag serves no purpose any
more and we can remove it, largely reverting 11086f2f2.

Use constant TRUE for "dummy" clauses when throwing back outer joins.
This improves on a hack I introduced in commit 6a6522529.  If we
have a left-join clause l.x = r.y, and a WHERE clause l.x = constant,
we generate r.y = constant and then don't really have a need for the
join clause.  But we must throw the join clause back anyway after
marking it redundant, so that the join search heuristics won't think
this is a clauseless join and avoid it.  That was a kluge introduced
under time pressure, and after looking at it I thought of a better
way: let's just introduce constant-TRUE "join clauses" instead,
and get rid of them at the end.  This improves the generated plans for
such cases by not having to test a redundant join clause.  We can also
get rid of the ugly hack used to mark such clauses as redundant for
selectivity estimation.

Patch by me; thanks to Richard Guo for review.

Discussion: https://postgr.es/m/830269.1656693747@sss.pgh.pa.us
This commit is contained in:
Tom Lane 2023-01-30 13:44:36 -05:00
parent 2489d76c49
commit b448f1c8d8
24 changed files with 324 additions and 762 deletions

View File

@ -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);

View File

@ -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);

View File

@ -715,12 +715,6 @@ clause_selectivity_ext(PlannerInfo *root,
return (Selectivity) 1.0;
}
/*
* 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

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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,

View File

@ -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))

View File

@ -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
{
@ -1345,6 +1298,33 @@ process_security_barrier_quals(PlannerInfo *root,
Assert(security_level <= root->qual_security_level);
}
/*
* 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);
/*
* 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;
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, 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 */
@ -2567,165 +2503,6 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
distribute_restrictinfo_to_rels(root, restrictinfo);
}
/*
* 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
@ -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)

View File

@ -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);
/*

View File

@ -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:

View File

@ -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);

View File

@ -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++;

View File

@ -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 */

View File

@ -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),
@ -284,102 +283,6 @@ find_placeholders_in_expr(PlannerInfo *root, Node *expr)
list_free(vars);
}
/*
* 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

View File

@ -283,6 +283,12 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
rel->top_parent = parent->top_parent ? parent->top_parent : 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
@ -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))

View File

@ -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);
}
/*
@ -436,6 +412,21 @@ restriction_is_securely_promotable(RestrictInfo *restrictinfo,
return false;
}
/*
* 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
*
@ -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.
* not pushing the clause into its outer-join outer 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.)
* 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;
}

View File

@ -268,14 +268,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
@ -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 */

View File

@ -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,

View File

@ -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,

View File

@ -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);

View File

@ -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,

View File

@ -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

View File

@ -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