mirror of
https://github.com/postgres/postgres.git
synced 2025-11-29 23:43:17 +03:00
Refactor planner's pathkeys data structure to create a separate, explicit
representation of equivalence classes of variables. This is an extensive rewrite, but it brings a number of benefits: * planner no longer fails in the presence of "incomplete" operator families that don't offer operators for every possible combination of datatypes. * avoid generating and then discarding redundant equality clauses. * remove bogus assumption that derived equalities always use operators named "=". * mergejoins can work with a variety of sort orders (e.g., descending) now, instead of tying each mergejoinable operator to exactly one sort order. * better recognition of redundant sort columns. * can make use of equalities appearing underneath an outer join.
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.127 2007/01/08 16:47:30 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.128 2007/01/20 20:45:39 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -37,8 +37,6 @@ int from_collapse_limit;
|
||||
int join_collapse_limit;
|
||||
|
||||
|
||||
static void add_vars_to_targetlist(PlannerInfo *root, List *vars,
|
||||
Relids where_needed);
|
||||
static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
|
||||
bool below_outer_join, Relids *qualscope);
|
||||
static OuterJoinInfo *make_outerjoininfo(PlannerInfo *root,
|
||||
@@ -51,8 +49,7 @@ static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
|
||||
Relids qualscope,
|
||||
Relids ojscope,
|
||||
Relids outerjoin_nonnullable);
|
||||
static bool qual_is_redundant(PlannerInfo *root, RestrictInfo *restrictinfo,
|
||||
List *restrictlist);
|
||||
static bool check_outerjoin_delay(PlannerInfo *root, Relids *relids_p);
|
||||
static void check_mergejoinable(RestrictInfo *restrictinfo);
|
||||
static void check_hashjoinable(RestrictInfo *restrictinfo);
|
||||
|
||||
@@ -144,7 +141,7 @@ build_base_rel_tlists(PlannerInfo *root, List *final_tlist)
|
||||
* as being needed for the indicated join (or for final output if
|
||||
* where_needed includes "relation 0").
|
||||
*/
|
||||
static void
|
||||
void
|
||||
add_vars_to_targetlist(PlannerInfo *root, List *vars, Relids where_needed)
|
||||
{
|
||||
ListCell *temp;
|
||||
@@ -590,17 +587,17 @@ make_outerjoininfo(PlannerInfo *root,
|
||||
* Add clause information to either the baserestrictinfo or joininfo list
|
||||
* (depending on whether the clause is a join) of each base relation
|
||||
* mentioned in the clause. A RestrictInfo node is created and added to
|
||||
* the appropriate list for each rel. Also, if the clause uses a
|
||||
* the appropriate list for each rel. Alternatively, if the clause uses a
|
||||
* mergejoinable operator and is not delayed by outer-join rules, enter
|
||||
* the left- and right-side expressions into the query's lists of
|
||||
* equijoined vars.
|
||||
* the left- and right-side expressions into the query's list of
|
||||
* EquivalenceClasses.
|
||||
*
|
||||
* 'clause': the qual clause to be distributed
|
||||
* 'is_pushed_down': if TRUE, force the clause to be marked 'is_pushed_down'
|
||||
* (this indicates the clause came from a FromExpr, not a JoinExpr)
|
||||
* 'is_deduced': TRUE if the qual came from implied-equality deduction
|
||||
* 'below_outer_join': TRUE if the qual is from a JOIN/ON that is below the
|
||||
* nullable side of a higher-level outer join.
|
||||
* nullable side of a higher-level outer join
|
||||
* 'qualscope': set of baserels the qual's syntactic scope covers
|
||||
* 'ojscope': NULL if not an outer-join qual, else the minimum set of baserels
|
||||
* needed to form this join
|
||||
@@ -625,11 +622,9 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
|
||||
Relids relids;
|
||||
bool outerjoin_delayed;
|
||||
bool pseudoconstant = false;
|
||||
bool maybe_equijoin;
|
||||
bool maybe_equivalence;
|
||||
bool maybe_outer_join;
|
||||
RestrictInfo *restrictinfo;
|
||||
RelOptInfo *rel;
|
||||
List *vars;
|
||||
|
||||
/*
|
||||
* Retrieve all relids mentioned within the clause.
|
||||
@@ -705,108 +700,57 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
|
||||
if (is_deduced)
|
||||
{
|
||||
/*
|
||||
* If the qual came from implied-equality deduction, we always
|
||||
* evaluate the qual at its natural semantic level. It is the
|
||||
* responsibility of the deducer not to create any quals that should
|
||||
* be delayed by outer-join rules.
|
||||
* If the qual came from implied-equality deduction, it should
|
||||
* not be outerjoin-delayed, else deducer blew it. But we can't
|
||||
* check this because the ojinfo list may now contain OJs above
|
||||
* where the qual belongs.
|
||||
*/
|
||||
Assert(bms_equal(relids, qualscope));
|
||||
Assert(!ojscope);
|
||||
Assert(!pseudoconstant);
|
||||
/* Needn't feed it back for more deductions */
|
||||
outerjoin_delayed = false;
|
||||
maybe_equijoin = false;
|
||||
/* Don't feed it back for more deductions */
|
||||
maybe_equivalence = false;
|
||||
maybe_outer_join = false;
|
||||
}
|
||||
else if (bms_overlap(relids, outerjoin_nonnullable))
|
||||
{
|
||||
/*
|
||||
* The qual is attached to an outer join and mentions (some of the)
|
||||
* rels on the nonnullable side. 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.
|
||||
* rels on the nonnullable side.
|
||||
*
|
||||
* Note: an outer-join qual that mentions only nullable-side rels can
|
||||
* be pushed down into the nullable side without changing the join
|
||||
* result, so we treat it the same as an ordinary inner-join qual,
|
||||
* except for not setting maybe_equijoin (see below).
|
||||
* result, so we treat it almost the same as an ordinary inner-join
|
||||
* qual (see below).
|
||||
*
|
||||
* We can't use such a clause to deduce equivalence (the left and right
|
||||
* sides might be unequal above the join because one of them has gone
|
||||
* to NULL) ... but we might be able to use it for more limited
|
||||
* deductions, if there are no lower outer joins that delay its
|
||||
* application. If so, consider adding it to the lists of set-aside
|
||||
* clauses.
|
||||
*/
|
||||
maybe_equivalence = false;
|
||||
maybe_outer_join = !check_outerjoin_delay(root, &relids);
|
||||
|
||||
/*
|
||||
* 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;
|
||||
outerjoin_delayed = true;
|
||||
Assert(!pseudoconstant);
|
||||
|
||||
/*
|
||||
* We can't use such a clause to deduce equijoin (the left and right
|
||||
* sides might be unequal above the join because one of them has gone
|
||||
* to NULL) ... but we might be able to use it for more limited
|
||||
* purposes. Note: for the current uses of deductions from an
|
||||
* outer-join clause, it seems safe to make the deductions even when
|
||||
* the clause is below a higher-level outer join; so we do not check
|
||||
* below_outer_join here.
|
||||
*/
|
||||
maybe_equijoin = false;
|
||||
maybe_outer_join = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* For a non-outer-join 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 oj_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 oj_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.
|
||||
*/
|
||||
bool found_some;
|
||||
|
||||
outerjoin_delayed = false;
|
||||
do {
|
||||
ListCell *l;
|
||||
|
||||
found_some = false;
|
||||
foreach(l, root->oj_info_list)
|
||||
{
|
||||
OuterJoinInfo *ojinfo = (OuterJoinInfo *) lfirst(l);
|
||||
|
||||
/* do we have any nullable rels of this OJ? */
|
||||
if (bms_overlap(relids, ojinfo->min_righthand) ||
|
||||
(ojinfo->is_full_join &&
|
||||
bms_overlap(relids, ojinfo->min_lefthand)))
|
||||
{
|
||||
/* yes; do we have all its rels? */
|
||||
if (!bms_is_subset(ojinfo->min_lefthand, relids) ||
|
||||
!bms_is_subset(ojinfo->min_righthand, relids))
|
||||
{
|
||||
/* no, so add them in */
|
||||
relids = bms_add_members(relids,
|
||||
ojinfo->min_lefthand);
|
||||
relids = bms_add_members(relids,
|
||||
ojinfo->min_righthand);
|
||||
outerjoin_delayed = true;
|
||||
/* we'll need another iteration */
|
||||
found_some = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (found_some);
|
||||
/* Normal qual clause; check to see if must be delayed by outer join */
|
||||
outerjoin_delayed = check_outerjoin_delay(root, &relids);
|
||||
|
||||
if (outerjoin_delayed)
|
||||
{
|
||||
@@ -816,26 +760,27 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
|
||||
* Because application of the qual will be delayed by outer join,
|
||||
* we mustn't assume its vars are equal everywhere.
|
||||
*/
|
||||
maybe_equijoin = false;
|
||||
maybe_equivalence = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Qual is not delayed by any lower outer-join restriction. If it
|
||||
* is not itself below or within an outer join, we can consider it
|
||||
* "valid everywhere", so consider feeding it to the equijoin
|
||||
* machinery. (If it is within an outer join, we can't consider
|
||||
* it "valid everywhere": once the contained variables have gone
|
||||
* to NULL, we'd be asserting things like NULL = NULL, which is
|
||||
* not true.)
|
||||
* Qual is not delayed by any lower outer-join restriction, so
|
||||
* we can consider feeding it 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).
|
||||
*/
|
||||
if (!below_outer_join && outerjoin_nonnullable == NULL)
|
||||
maybe_equijoin = true;
|
||||
else
|
||||
maybe_equijoin = false;
|
||||
maybe_equivalence = true;
|
||||
if (outerjoin_nonnullable != NULL)
|
||||
below_outer_join = true;
|
||||
}
|
||||
|
||||
/* Since it doesn't mention the LHS, it's certainly not an OJ clause */
|
||||
/*
|
||||
* Since it doesn't mention the LHS, it's certainly not useful as a
|
||||
* set-aside OJ clause, even if it's in an OJ.
|
||||
*/
|
||||
maybe_outer_join = false;
|
||||
}
|
||||
|
||||
@@ -860,118 +805,65 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
|
||||
relids);
|
||||
|
||||
/*
|
||||
* Figure out where to attach it.
|
||||
* If it's a join clause (either naturally, or because delayed by
|
||||
* outer-join rules), add vars used in the clause to targetlists of
|
||||
* their relations, so that they will be emitted by the plan nodes that
|
||||
* scan those relations (else they won't be available at the join node!).
|
||||
*
|
||||
* Note: if the clause gets absorbed into an EquivalenceClass then this
|
||||
* may be unnecessary, but for now we have to do it to cover the case
|
||||
* where the EC becomes ec_broken and we end up reinserting the original
|
||||
* clauses into the plan.
|
||||
*/
|
||||
switch (bms_membership(relids))
|
||||
if (bms_membership(relids) == BMS_MULTIPLE)
|
||||
{
|
||||
case BMS_SINGLETON:
|
||||
List *vars = pull_var_clause(clause, false);
|
||||
|
||||
/*
|
||||
* There is only one relation participating in 'clause', so
|
||||
* 'clause' is a restriction clause for that relation.
|
||||
*/
|
||||
rel = find_base_rel(root, bms_singleton_member(relids));
|
||||
|
||||
/*
|
||||
* Check for a "mergejoinable" clause even though it's not a join
|
||||
* clause. This is so that we can recognize that "a.x = a.y"
|
||||
* makes x and y eligible to be considered equal, even when they
|
||||
* belong to the same rel. Without this, we would not recognize
|
||||
* that "a.x = a.y AND a.x = b.z AND a.y = c.q" allows us to
|
||||
* consider z and q equal after their rels are joined.
|
||||
*/
|
||||
check_mergejoinable(restrictinfo);
|
||||
|
||||
/*
|
||||
* If the clause was deduced from implied equality, check to see
|
||||
* whether it is redundant with restriction clauses we already
|
||||
* have for this rel. Note we cannot apply this check to
|
||||
* user-written clauses, since we haven't found the canonical
|
||||
* pathkey sets yet while processing user clauses. (NB: no
|
||||
* comparable check is done in the join-clause case; redundancy
|
||||
* will be detected when the join clause is moved into a join
|
||||
* rel's restriction list.)
|
||||
*/
|
||||
if (!is_deduced ||
|
||||
!qual_is_redundant(root, restrictinfo,
|
||||
rel->baserestrictinfo))
|
||||
{
|
||||
/* Add clause to rel's restriction list */
|
||||
rel->baserestrictinfo = lappend(rel->baserestrictinfo,
|
||||
restrictinfo);
|
||||
}
|
||||
break;
|
||||
case BMS_MULTIPLE:
|
||||
|
||||
/*
|
||||
* 'clause' is a join clause, since there is more than one rel in
|
||||
* the relid set.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Check for hash or mergejoinable operators.
|
||||
*
|
||||
* We don't bother setting the hashjoin info if we're not going to
|
||||
* need it. We do want to know about mergejoinable ops in all
|
||||
* cases, however, because we use mergejoinable ops for other
|
||||
* purposes such as detecting redundant clauses.
|
||||
*/
|
||||
check_mergejoinable(restrictinfo);
|
||||
if (enable_hashjoin)
|
||||
check_hashjoinable(restrictinfo);
|
||||
|
||||
/*
|
||||
* Add clause to the join lists of all the relevant relations.
|
||||
*/
|
||||
add_join_clause_to_rels(root, restrictinfo, relids);
|
||||
|
||||
/*
|
||||
* Add vars used in the join clause to targetlists of their
|
||||
* relations, so that they will be emitted by the plan nodes that
|
||||
* scan those relations (else they won't be available at the join
|
||||
* node!).
|
||||
*/
|
||||
vars = pull_var_clause(clause, false);
|
||||
add_vars_to_targetlist(root, vars, relids);
|
||||
list_free(vars);
|
||||
break;
|
||||
default:
|
||||
|
||||
/*
|
||||
* 'clause' references no rels, and therefore we have no place to
|
||||
* attach it. Shouldn't get here if callers are working properly.
|
||||
*/
|
||||
elog(ERROR, "cannot cope with variable-free clause");
|
||||
break;
|
||||
add_vars_to_targetlist(root, vars, relids);
|
||||
list_free(vars);
|
||||
}
|
||||
|
||||
/*
|
||||
* If the clause has a mergejoinable operator, we may be able to deduce
|
||||
* more things from it under the principle of transitivity.
|
||||
* We check "mergejoinability" of every clause, not only join clauses,
|
||||
* because we want to know about equivalences between vars of the same
|
||||
* relation, or between vars and consts.
|
||||
*/
|
||||
check_mergejoinable(restrictinfo);
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* If it is not an outer-join qualification nor bubbled up due to an outer
|
||||
* join, then the two sides represent equivalent PathKeyItems for path
|
||||
* keys: any path that is sorted by one side will also be sorted by the
|
||||
* other (as soon as the two rels are joined, that is). Pass such clauses
|
||||
* to add_equijoined_keys.
|
||||
* If the clause has a mergejoinable operator and is not outerjoin-delayed,
|
||||
* yet isn't an equivalence because it is an outer-join clause, the EC
|
||||
* code may yet be able to do something with it. We add it to appropriate
|
||||
* lists for further consideration later. Specifically:
|
||||
*
|
||||
* If it is a left or right outer-join qualification that relates the two
|
||||
* sides of the outer join (no funny business like leftvar1 = leftvar2 +
|
||||
* rightvar), we add it to root->left_join_clauses or
|
||||
* If it is a left or right outer-join qualification that relates the
|
||||
* two sides of the outer join (no funny business like leftvar1 =
|
||||
* leftvar2 + rightvar), we add it to root->left_join_clauses or
|
||||
* root->right_join_clauses according to which side the nonnullable
|
||||
* variable appears on.
|
||||
*
|
||||
* If it is a full outer-join qualification, we add it to
|
||||
* root->full_join_clauses. (Ideally we'd discard cases that aren't
|
||||
* leftvar = rightvar, as we do for left/right joins, but this routine
|
||||
* doesn't have the info needed to do that; and the current usage of the
|
||||
* full_join_clauses list doesn't require that, so it's not currently
|
||||
* worth complicating this routine's API to make it possible.)
|
||||
* doesn't have the info needed to do that; and the current usage of
|
||||
* the full_join_clauses list doesn't require that, so it's not
|
||||
* currently worth complicating this routine's API to make it possible.)
|
||||
*
|
||||
* If none of the above hold, pass it off to
|
||||
* distribute_restrictinfo_to_rels().
|
||||
*/
|
||||
if (restrictinfo->mergejoinoperator != InvalidOid)
|
||||
if (restrictinfo->mergeopfamilies)
|
||||
{
|
||||
if (maybe_equijoin)
|
||||
add_equijoined_keys(root, restrictinfo);
|
||||
if (maybe_equivalence)
|
||||
{
|
||||
if (process_equivalence(root, restrictinfo, below_outer_join))
|
||||
return;
|
||||
/* EC rejected it, so pass to distribute_restrictinfo_to_rels */
|
||||
}
|
||||
else if (maybe_outer_join && restrictinfo->can_join)
|
||||
{
|
||||
if (bms_is_subset(restrictinfo->left_relids,
|
||||
@@ -982,8 +874,9 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
|
||||
/* we have outervar = innervar */
|
||||
root->left_join_clauses = lappend(root->left_join_clauses,
|
||||
restrictinfo);
|
||||
return;
|
||||
}
|
||||
else if (bms_is_subset(restrictinfo->right_relids,
|
||||
if (bms_is_subset(restrictinfo->right_relids,
|
||||
outerjoin_nonnullable) &&
|
||||
!bms_overlap(restrictinfo->left_relids,
|
||||
outerjoin_nonnullable))
|
||||
@@ -991,166 +884,213 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
|
||||
/* we have innervar = outervar */
|
||||
root->right_join_clauses = lappend(root->right_join_clauses,
|
||||
restrictinfo);
|
||||
return;
|
||||
}
|
||||
else if (bms_equal(outerjoin_nonnullable, qualscope))
|
||||
if (bms_equal(outerjoin_nonnullable, qualscope))
|
||||
{
|
||||
/* FULL JOIN (above tests cannot match in this case) */
|
||||
root->full_join_clauses = lappend(root->full_join_clauses,
|
||||
restrictinfo);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* No EC special case applies, so push it into the clause lists */
|
||||
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.
|
||||
*
|
||||
* If so, add relids to *relids_p to reflect the lowest safe level for
|
||||
* evaluating the qual, and return TRUE.
|
||||
*
|
||||
* For a non-outer-join 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 oj_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 oj_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 an outer-join qual, this isn't going to determine where we place the
|
||||
* qual, but we need to determine outerjoin_delayed anyway so we can decide
|
||||
* whether the qual is potentially useful for equivalence deductions.
|
||||
*/
|
||||
static bool
|
||||
check_outerjoin_delay(PlannerInfo *root, Relids *relids_p)
|
||||
{
|
||||
Relids relids = *relids_p;
|
||||
bool outerjoin_delayed;
|
||||
bool found_some;
|
||||
|
||||
outerjoin_delayed = false;
|
||||
do {
|
||||
ListCell *l;
|
||||
|
||||
found_some = false;
|
||||
foreach(l, root->oj_info_list)
|
||||
{
|
||||
OuterJoinInfo *ojinfo = (OuterJoinInfo *) lfirst(l);
|
||||
|
||||
/* do we reference any nullable rels of this OJ? */
|
||||
if (bms_overlap(relids, ojinfo->min_righthand) ||
|
||||
(ojinfo->is_full_join &&
|
||||
bms_overlap(relids, ojinfo->min_lefthand)))
|
||||
{
|
||||
/* yes; have we included all its rels in relids? */
|
||||
if (!bms_is_subset(ojinfo->min_lefthand, relids) ||
|
||||
!bms_is_subset(ojinfo->min_righthand, relids))
|
||||
{
|
||||
/* no, so add them in */
|
||||
relids = bms_add_members(relids, ojinfo->min_lefthand);
|
||||
relids = bms_add_members(relids, ojinfo->min_righthand);
|
||||
outerjoin_delayed = true;
|
||||
/* we'll need another iteration */
|
||||
found_some = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (found_some);
|
||||
|
||||
*relids_p = relids;
|
||||
return outerjoin_delayed;
|
||||
}
|
||||
|
||||
/*
|
||||
* distribute_restrictinfo_to_rels
|
||||
* Push a completed RestrictInfo into the proper restriction or join
|
||||
* clause list(s).
|
||||
*
|
||||
* This is the last step of distribute_qual_to_rels() for ordinary qual
|
||||
* clauses. Clauses that are interesting for equivalence-class processing
|
||||
* are diverted to the EC machinery, but may ultimately get fed back here.
|
||||
*/
|
||||
void
|
||||
distribute_restrictinfo_to_rels(PlannerInfo *root,
|
||||
RestrictInfo *restrictinfo)
|
||||
{
|
||||
Relids relids = restrictinfo->required_relids;
|
||||
RelOptInfo *rel;
|
||||
|
||||
switch (bms_membership(relids))
|
||||
{
|
||||
case BMS_SINGLETON:
|
||||
|
||||
/*
|
||||
* There is only one relation participating in the clause, so
|
||||
* it is a restriction clause for that relation.
|
||||
*/
|
||||
rel = find_base_rel(root, bms_singleton_member(relids));
|
||||
|
||||
/* Add clause to rel's restriction list */
|
||||
rel->baserestrictinfo = lappend(rel->baserestrictinfo,
|
||||
restrictinfo);
|
||||
break;
|
||||
case BMS_MULTIPLE:
|
||||
|
||||
/*
|
||||
* The clause is a join clause, since there is more than one rel
|
||||
* in its relid set.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Check for hashjoinable operators. (We don't bother setting
|
||||
* the hashjoin info if we're not going to need it.)
|
||||
*/
|
||||
if (enable_hashjoin)
|
||||
check_hashjoinable(restrictinfo);
|
||||
|
||||
/*
|
||||
* Add clause to the join lists of all the relevant relations.
|
||||
*/
|
||||
add_join_clause_to_rels(root, restrictinfo, relids);
|
||||
break;
|
||||
default:
|
||||
|
||||
/*
|
||||
* clause references no rels, and therefore we have no place to
|
||||
* attach it. Shouldn't get here if callers are working properly.
|
||||
*/
|
||||
elog(ERROR, "cannot cope with variable-free clause");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* process_implied_equality
|
||||
* Check to see whether we already have a restrictinfo item that says
|
||||
* item1 = item2, and create one if not; or if delete_it is true,
|
||||
* remove any such restrictinfo item.
|
||||
* Create a restrictinfo item that says "item1 op item2", and push it
|
||||
* into the appropriate lists. (In practice opno is always a btree
|
||||
* equality operator.)
|
||||
*
|
||||
* This processing is a consequence of transitivity of mergejoin equality:
|
||||
* if we have mergejoinable clauses A = B and B = C, we can deduce A = C
|
||||
* (where = is an appropriate mergejoinable operator). See path/pathkeys.c
|
||||
* for more details.
|
||||
* "qualscope" is the nominal syntactic level to impute to the restrictinfo.
|
||||
* This must contain at least all the rels used in the expressions, but it
|
||||
* is used only to set the qual application level when both exprs are
|
||||
* variable-free. Otherwise the qual is applied at the lowest join level
|
||||
* that provides all its variables.
|
||||
*
|
||||
* "both_const" indicates whether both items are known pseudo-constant;
|
||||
* in this case it is worth applying eval_const_expressions() in case we
|
||||
* can produce constant TRUE or constant FALSE. (Otherwise it's not,
|
||||
* because the expressions went through eval_const_expressions already.)
|
||||
*
|
||||
* This is currently used only when an EquivalenceClass is found to
|
||||
* contain pseudoconstants. See path/pathkeys.c for more details.
|
||||
*/
|
||||
void
|
||||
process_implied_equality(PlannerInfo *root,
|
||||
Node *item1, Node *item2,
|
||||
Oid sortop1, Oid sortop2,
|
||||
Relids item1_relids, Relids item2_relids,
|
||||
bool delete_it)
|
||||
Oid opno,
|
||||
Expr *item1,
|
||||
Expr *item2,
|
||||
Relids qualscope,
|
||||
bool below_outer_join,
|
||||
bool both_const)
|
||||
{
|
||||
Relids relids;
|
||||
BMS_Membership membership;
|
||||
RelOptInfo *rel1;
|
||||
List *restrictlist;
|
||||
ListCell *itm;
|
||||
Oid ltype,
|
||||
rtype;
|
||||
Operator eq_operator;
|
||||
Form_pg_operator pgopform;
|
||||
Expr *clause;
|
||||
|
||||
/* Get set of relids referenced in the two expressions */
|
||||
relids = bms_union(item1_relids, item2_relids);
|
||||
membership = bms_membership(relids);
|
||||
|
||||
/*
|
||||
* generate_implied_equalities() shouldn't call me on two constants.
|
||||
* Build the new clause. Copy to ensure it shares no substructure with
|
||||
* original (this is necessary in case there are subselects in there...)
|
||||
*/
|
||||
Assert(membership != BMS_EMPTY_SET);
|
||||
|
||||
/*
|
||||
* If the exprs involve a single rel, we need to look at that rel's
|
||||
* baserestrictinfo list. If multiple rels, we can scan the joininfo list
|
||||
* of any of 'em.
|
||||
*/
|
||||
if (membership == BMS_SINGLETON)
|
||||
{
|
||||
rel1 = find_base_rel(root, bms_singleton_member(relids));
|
||||
restrictlist = rel1->baserestrictinfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
Relids other_rels;
|
||||
int first_rel;
|
||||
|
||||
/* Copy relids, find and remove one member */
|
||||
other_rels = bms_copy(relids);
|
||||
first_rel = bms_first_member(other_rels);
|
||||
bms_free(other_rels);
|
||||
|
||||
rel1 = find_base_rel(root, first_rel);
|
||||
restrictlist = rel1->joininfo;
|
||||
}
|
||||
|
||||
/*
|
||||
* Scan to see if equality is already known. If so, we're done in the add
|
||||
* case, and done after removing it in the delete case.
|
||||
*/
|
||||
foreach(itm, restrictlist)
|
||||
{
|
||||
RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(itm);
|
||||
Node *left,
|
||||
*right;
|
||||
|
||||
if (restrictinfo->mergejoinoperator == InvalidOid)
|
||||
continue; /* ignore non-mergejoinable clauses */
|
||||
/* We now know the restrictinfo clause is a binary opclause */
|
||||
left = get_leftop(restrictinfo->clause);
|
||||
right = get_rightop(restrictinfo->clause);
|
||||
if ((equal(item1, left) && equal(item2, right)) ||
|
||||
(equal(item2, left) && equal(item1, right)))
|
||||
{
|
||||
/* found a matching clause */
|
||||
if (delete_it)
|
||||
{
|
||||
if (membership == BMS_SINGLETON)
|
||||
{
|
||||
/* delete it from local restrictinfo list */
|
||||
rel1->baserestrictinfo = list_delete_ptr(rel1->baserestrictinfo,
|
||||
restrictinfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* let joininfo.c do it */
|
||||
remove_join_clause_from_rels(root, restrictinfo, relids);
|
||||
}
|
||||
}
|
||||
return; /* done */
|
||||
}
|
||||
}
|
||||
|
||||
/* Didn't find it. Done if deletion requested */
|
||||
if (delete_it)
|
||||
return;
|
||||
|
||||
/*
|
||||
* This equality is new information, so construct a clause representing it
|
||||
* to add to the query data structures.
|
||||
*/
|
||||
ltype = exprType(item1);
|
||||
rtype = exprType(item2);
|
||||
eq_operator = compatible_oper(NULL, list_make1(makeString("=")),
|
||||
ltype, rtype,
|
||||
true, -1);
|
||||
if (!HeapTupleIsValid(eq_operator))
|
||||
{
|
||||
/*
|
||||
* Would it be safe to just not add the equality to the query if we
|
||||
* have no suitable equality operator for the combination of
|
||||
* datatypes? NO, because sortkey selection may screw up anyway.
|
||||
*/
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_FUNCTION),
|
||||
errmsg("could not identify an equality operator for types %s and %s",
|
||||
format_type_be(ltype), format_type_be(rtype))));
|
||||
}
|
||||
pgopform = (Form_pg_operator) GETSTRUCT(eq_operator);
|
||||
|
||||
/*
|
||||
* Let's just make sure this appears to be a compatible operator.
|
||||
*
|
||||
* XXX needs work
|
||||
*/
|
||||
if (pgopform->oprresult != BOOLOID)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
|
||||
errmsg("equality operator for types %s and %s should be merge-joinable, but isn't",
|
||||
format_type_be(ltype), format_type_be(rtype))));
|
||||
|
||||
/*
|
||||
* Now we can build the new clause. Copy to ensure it shares no
|
||||
* substructure with original (this is necessary in case there are
|
||||
* subselects in there...)
|
||||
*/
|
||||
clause = make_opclause(oprid(eq_operator), /* opno */
|
||||
clause = make_opclause(opno,
|
||||
BOOLOID, /* opresulttype */
|
||||
false, /* opretset */
|
||||
(Expr *) copyObject(item1),
|
||||
(Expr *) copyObject(item2));
|
||||
|
||||
ReleaseSysCache(eq_operator);
|
||||
/* If both constant, try to reduce to a boolean constant. */
|
||||
if (both_const)
|
||||
{
|
||||
clause = (Expr *) eval_const_expressions((Node *) clause);
|
||||
|
||||
/* If we produced const TRUE, just drop the clause */
|
||||
if (clause && IsA(clause, Const))
|
||||
{
|
||||
Const *cclause = (Const *) clause;
|
||||
|
||||
Assert(cclause->consttype == BOOLOID);
|
||||
if (!cclause->constisnull && DatumGetBool(cclause->constvalue))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Make a copy of qualscope to avoid problems if source EC changes */
|
||||
qualscope = bms_copy(qualscope);
|
||||
|
||||
/*
|
||||
* Push the new clause into all the appropriate restrictinfo lists.
|
||||
@@ -1159,119 +1099,53 @@ process_implied_equality(PlannerInfo *root,
|
||||
* taken for an original JOIN/ON clause.
|
||||
*/
|
||||
distribute_qual_to_rels(root, (Node *) clause,
|
||||
true, true, false, relids, NULL, NULL);
|
||||
true, true, below_outer_join,
|
||||
qualscope, NULL, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* qual_is_redundant
|
||||
* Detect whether an implied-equality qual that turns out to be a
|
||||
* restriction clause for a single base relation is redundant with
|
||||
* already-known restriction clauses for that rel. This occurs with,
|
||||
* for example,
|
||||
* SELECT * FROM tab WHERE f1 = f2 AND f2 = f3;
|
||||
* We need to suppress the redundant condition to avoid computing
|
||||
* too-small selectivity, not to mention wasting time at execution.
|
||||
* build_implied_join_equality --- build a RestrictInfo for a derived equality
|
||||
*
|
||||
* Note: quals of the form "var = const" are never considered redundant,
|
||||
* only those of the form "var = var". This is needed because when we
|
||||
* have constants in an implied-equality set, we use a different strategy
|
||||
* that suppresses all "var = var" deductions. We must therefore keep
|
||||
* all the "var = const" quals.
|
||||
* This overlaps the functionality of process_implied_equality(), but we
|
||||
* must return the RestrictInfo, not push it into the joininfo tree.
|
||||
*/
|
||||
static bool
|
||||
qual_is_redundant(PlannerInfo *root,
|
||||
RestrictInfo *restrictinfo,
|
||||
List *restrictlist)
|
||||
RestrictInfo *
|
||||
build_implied_join_equality(Oid opno,
|
||||
Expr *item1,
|
||||
Expr *item2,
|
||||
Relids qualscope)
|
||||
{
|
||||
Node *newleft;
|
||||
Node *newright;
|
||||
List *oldquals;
|
||||
ListCell *olditem;
|
||||
List *equalexprs;
|
||||
bool someadded;
|
||||
|
||||
/* Never redundant unless vars appear on both sides */
|
||||
if (bms_is_empty(restrictinfo->left_relids) ||
|
||||
bms_is_empty(restrictinfo->right_relids))
|
||||
return false;
|
||||
|
||||
newleft = get_leftop(restrictinfo->clause);
|
||||
newright = get_rightop(restrictinfo->clause);
|
||||
RestrictInfo *restrictinfo;
|
||||
Expr *clause;
|
||||
|
||||
/*
|
||||
* Set cached pathkeys. NB: it is okay to do this now because this
|
||||
* routine is only invoked while we are generating implied equalities.
|
||||
* Therefore, the equi_key_list is already complete and so we can
|
||||
* correctly determine canonical pathkeys.
|
||||
* Build the new clause. Copy to ensure it shares no substructure with
|
||||
* original (this is necessary in case there are subselects in there...)
|
||||
*/
|
||||
cache_mergeclause_pathkeys(root, restrictinfo);
|
||||
/* If different, say "not redundant" (should never happen) */
|
||||
if (restrictinfo->left_pathkey != restrictinfo->right_pathkey)
|
||||
return false;
|
||||
clause = make_opclause(opno,
|
||||
BOOLOID, /* opresulttype */
|
||||
false, /* opretset */
|
||||
(Expr *) copyObject(item1),
|
||||
(Expr *) copyObject(item2));
|
||||
|
||||
/* Make a copy of qualscope to avoid problems if source EC changes */
|
||||
qualscope = bms_copy(qualscope);
|
||||
|
||||
/*
|
||||
* Scan existing quals to find those referencing same pathkeys. Usually
|
||||
* there will be few, if any, so build a list of just the interesting
|
||||
* ones.
|
||||
* Build the RestrictInfo node itself.
|
||||
*/
|
||||
oldquals = NIL;
|
||||
foreach(olditem, restrictlist)
|
||||
{
|
||||
RestrictInfo *oldrinfo = (RestrictInfo *) lfirst(olditem);
|
||||
restrictinfo = make_restrictinfo(clause,
|
||||
true, /* is_pushed_down */
|
||||
false, /* outerjoin_delayed */
|
||||
false, /* pseudoconstant */
|
||||
qualscope);
|
||||
|
||||
if (oldrinfo->mergejoinoperator != InvalidOid)
|
||||
{
|
||||
cache_mergeclause_pathkeys(root, oldrinfo);
|
||||
if (restrictinfo->left_pathkey == oldrinfo->left_pathkey &&
|
||||
restrictinfo->right_pathkey == oldrinfo->right_pathkey)
|
||||
oldquals = lcons(oldrinfo, oldquals);
|
||||
}
|
||||
}
|
||||
if (oldquals == NIL)
|
||||
return false;
|
||||
/* Set mergejoinability info always, and hashjoinability if enabled */
|
||||
check_mergejoinable(restrictinfo);
|
||||
if (enable_hashjoin)
|
||||
check_hashjoinable(restrictinfo);
|
||||
|
||||
/*
|
||||
* Now, we want to develop a list of exprs that are known equal to the
|
||||
* left side of the new qual. We traverse the old-quals list repeatedly
|
||||
* to transitively expand the exprs list. If at any point we find we can
|
||||
* reach the right-side expr of the new qual, we are done. We give up
|
||||
* when we can't expand the equalexprs list any more.
|
||||
*/
|
||||
equalexprs = list_make1(newleft);
|
||||
do
|
||||
{
|
||||
someadded = false;
|
||||
/* cannot use foreach here because of possible list_delete */
|
||||
olditem = list_head(oldquals);
|
||||
while (olditem)
|
||||
{
|
||||
RestrictInfo *oldrinfo = (RestrictInfo *) lfirst(olditem);
|
||||
Node *oldleft = get_leftop(oldrinfo->clause);
|
||||
Node *oldright = get_rightop(oldrinfo->clause);
|
||||
Node *newguy = NULL;
|
||||
|
||||
/* must advance olditem before list_delete possibly pfree's it */
|
||||
olditem = lnext(olditem);
|
||||
|
||||
if (list_member(equalexprs, oldleft))
|
||||
newguy = oldright;
|
||||
else if (list_member(equalexprs, oldright))
|
||||
newguy = oldleft;
|
||||
else
|
||||
continue;
|
||||
if (equal(newguy, newright))
|
||||
return true; /* we proved new clause is redundant */
|
||||
equalexprs = lcons(newguy, equalexprs);
|
||||
someadded = true;
|
||||
|
||||
/*
|
||||
* Remove this qual from list, since we don't need it anymore.
|
||||
*/
|
||||
oldquals = list_delete_ptr(oldquals, oldrinfo);
|
||||
}
|
||||
} while (someadded);
|
||||
|
||||
return false; /* it's not redundant */
|
||||
return restrictinfo;
|
||||
}
|
||||
|
||||
|
||||
@@ -1294,10 +1168,7 @@ static void
|
||||
check_mergejoinable(RestrictInfo *restrictinfo)
|
||||
{
|
||||
Expr *clause = restrictinfo->clause;
|
||||
Oid opno,
|
||||
leftOp,
|
||||
rightOp;
|
||||
Oid opfamily;
|
||||
Oid opno;
|
||||
|
||||
if (restrictinfo->pseudoconstant)
|
||||
return;
|
||||
@@ -1310,16 +1181,13 @@ check_mergejoinable(RestrictInfo *restrictinfo)
|
||||
|
||||
if (op_mergejoinable(opno) &&
|
||||
!contain_volatile_functions((Node *) clause))
|
||||
{
|
||||
/* XXX for the moment, continue to force use of particular sortops */
|
||||
if (get_op_mergejoin_info(opno, &leftOp, &rightOp, &opfamily))
|
||||
{
|
||||
restrictinfo->mergejoinoperator = opno;
|
||||
restrictinfo->left_sortop = leftOp;
|
||||
restrictinfo->right_sortop = rightOp;
|
||||
restrictinfo->mergeopfamily = opfamily;
|
||||
}
|
||||
}
|
||||
restrictinfo->mergeopfamilies = get_mergejoin_opfamilies(opno);
|
||||
|
||||
/*
|
||||
* Note: op_mergejoinable is just a hint; if we fail to find the
|
||||
* operator in any btree opfamilies, mergeopfamilies remains NIL
|
||||
* and so the clause is not treated as mergejoinable.
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user