1
0
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:
Tom Lane
2007-01-20 20:45:41 +00:00
parent 2b7334d487
commit f41803bb39
35 changed files with 3882 additions and 2719 deletions

View File

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