mirror of
https://github.com/postgres/postgres.git
synced 2025-07-15 19:21:59 +03:00
Teach planner about some cases where a restriction clause can be
propagated inside an outer join. In particular, given LEFT JOIN ON (A = B) WHERE A = constant, we cannot conclude that B = constant at the top level (B might be null instead), but we can nonetheless put a restriction B = constant into the quals for B's relation, since no inner-side rows not meeting that condition can contribute to the final result. Similarly, given FULL JOIN USING (J) WHERE J = constant, we can't directly conclude that either input J variable = constant, but it's OK to push such quals into each input rel. Per recent gripe from Kim Bisgaard. Along the way, remove 'valid_everywhere' flag from RestrictInfo, as on closer analysis it was not being used for anything, and was defined backwards anyway.
This commit is contained in:
@ -11,7 +11,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/path/pathkeys.c,v 1.68 2005/06/09 04:18:59 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/path/pathkeys.c,v 1.69 2005/07/02 23:00:40 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -32,6 +32,14 @@
|
||||
|
||||
|
||||
static PathKeyItem *makePathKeyItem(Node *key, Oid sortop, bool checkType);
|
||||
static void generate_outer_join_implications(PlannerInfo *root,
|
||||
List *equi_key_set,
|
||||
Relids *relids);
|
||||
static void process_implied_const_eq(PlannerInfo *root,
|
||||
List *equi_key_set, Relids *relids,
|
||||
Node *item1, Oid sortop1,
|
||||
Relids item1_relids,
|
||||
bool delete_it);
|
||||
static List *make_canonical_pathkey(PlannerInfo *root, PathKeyItem *item);
|
||||
static Var *find_indexkey_var(PlannerInfo *root, RelOptInfo *rel,
|
||||
AttrNumber varattno);
|
||||
@ -193,6 +201,10 @@ add_equijoined_keys(PlannerInfo *root, RestrictInfo *restrictinfo)
|
||||
* functions; but we will never consider such an expression to be a pathkey
|
||||
* at all, because check_mergejoinable() will reject it.)
|
||||
*
|
||||
* Also, when we have constants in an equi_key_list we can try to propagate
|
||||
* the constants into outer joins; see generate_outer_join_implications
|
||||
* for discussion.
|
||||
*
|
||||
* This routine just walks the equi_key_list to find all pairwise equalities.
|
||||
* We call process_implied_equality (in plan/initsplan.c) to adjust the
|
||||
* restrictinfo datastructures for each pair.
|
||||
@ -213,9 +225,10 @@ generate_implied_equalities(PlannerInfo *root)
|
||||
|
||||
/*
|
||||
* A set containing only two items cannot imply any equalities
|
||||
* beyond the one that created the set, so we can skip it.
|
||||
* beyond the one that created the set, so we can skip it ---
|
||||
* unless outer joins appear in the query.
|
||||
*/
|
||||
if (nitems < 3)
|
||||
if (nitems < 3 && !root->hasOuterJoins)
|
||||
continue;
|
||||
|
||||
/*
|
||||
@ -237,6 +250,20 @@ generate_implied_equalities(PlannerInfo *root)
|
||||
i1++;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we have constant(s) and outer joins, try to propagate the
|
||||
* constants through outer-join quals.
|
||||
*/
|
||||
if (have_consts && root->hasOuterJoins)
|
||||
generate_outer_join_implications(root, curset, relids);
|
||||
|
||||
/*
|
||||
* A set containing only two items cannot imply any equalities
|
||||
* beyond the one that created the set, so we can skip it.
|
||||
*/
|
||||
if (nitems < 3)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Match each item in the set with all that appear after it (it's
|
||||
* sufficient to generate A=B, need not process B=A too).
|
||||
@ -285,6 +312,264 @@ generate_implied_equalities(PlannerInfo *root)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* generate_outer_join_implications
|
||||
* Generate clauses that can be deduced in outer-join situations.
|
||||
*
|
||||
* When we have mergejoinable clauses A = B that are outer-join clauses,
|
||||
* we can't blindly combine them with other clauses A = C to deduce B = C,
|
||||
* since in fact the "equality" A = B won't necessarily hold above the
|
||||
* outer join (one of the variables might be NULL instead). Nonetheless
|
||||
* there are cases where we can add qual clauses using transitivity.
|
||||
*
|
||||
* One case that we look for here is an outer-join clause OUTERVAR = INNERVAR
|
||||
* combined with a pushed-down (valid everywhere) clause OUTERVAR = CONSTANT.
|
||||
* It is safe and useful to push a clause INNERVAR = CONSTANT into the
|
||||
* evaluation of the inner (nullable) relation, because any inner rows not
|
||||
* meeting this condition will not contribute to the outer-join result anyway.
|
||||
* (Any outer rows they could join to will be eliminated by the pushed-down
|
||||
* clause.)
|
||||
*
|
||||
* Note that the above rule does not work for full outer joins, nor for
|
||||
* pushed-down restrictions on an inner-side variable; nor is it very
|
||||
* interesting to consider cases where the pushed-down clause involves
|
||||
* relations entirely outside the outer join, since such clauses couldn't
|
||||
* be pushed into the inner side's scan anyway. So the restriction to
|
||||
* outervar = pseudoconstant is not really giving up anything.
|
||||
*
|
||||
* For full-join cases, we can only do something useful if it's a FULL JOIN
|
||||
* USING and a merged column has a restriction MERGEDVAR = CONSTANT. By
|
||||
* the time it gets here, the restriction will look like
|
||||
* COALESCE(LEFTVAR, RIGHTVAR) = CONSTANT
|
||||
* and we will have a join clause LEFTVAR = RIGHTVAR that we can match the
|
||||
* COALESCE expression to. In this situation we can push LEFTVAR = CONSTANT
|
||||
* and RIGHTVAR = CONSTANT into the input relations, since any rows not
|
||||
* meeting these conditions cannot contribute to the join result.
|
||||
*
|
||||
* Again, there isn't any traction to be gained by trying to deal with
|
||||
* clauses comparing a mergedvar to a non-pseudoconstant. So we can make
|
||||
* use of the equi_key_lists to quickly find the interesting pushed-down
|
||||
* clauses. The interesting outer-join clauses were accumulated for us by
|
||||
* distribute_qual_to_rels.
|
||||
*
|
||||
* equi_key_set: a list of PathKeyItems that are known globally equivalent,
|
||||
* at least one of which is a pseudoconstant.
|
||||
* relids: an array of Relids sets showing the relation membership of each
|
||||
* PathKeyItem in equi_key_set.
|
||||
*/
|
||||
static void
|
||||
generate_outer_join_implications(PlannerInfo *root,
|
||||
List *equi_key_set,
|
||||
Relids *relids)
|
||||
{
|
||||
ListCell *l1;
|
||||
|
||||
/* Examine each mergejoinable outer-join clause with OUTERVAR on left */
|
||||
foreach(l1, root->left_join_clauses)
|
||||
{
|
||||
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l1);
|
||||
Node *leftop = get_leftop(rinfo->clause);
|
||||
Node *rightop = get_rightop(rinfo->clause);
|
||||
ListCell *l2;
|
||||
|
||||
/* Scan to see if it matches any element of equi_key_set */
|
||||
foreach(l2, equi_key_set)
|
||||
{
|
||||
PathKeyItem *item1 = (PathKeyItem *) lfirst(l2);
|
||||
|
||||
if (equal(leftop, item1->key) &&
|
||||
rinfo->left_sortop == item1->sortop)
|
||||
{
|
||||
/*
|
||||
* Yes, so find constant member(s) of set and generate
|
||||
* implied INNERVAR = CONSTANT
|
||||
*/
|
||||
process_implied_const_eq(root, equi_key_set, relids,
|
||||
rightop,
|
||||
rinfo->right_sortop,
|
||||
rinfo->right_relids,
|
||||
false);
|
||||
/*
|
||||
* We can remove the explicit outer join qual, too,
|
||||
* since we now have tests forcing each of its sides
|
||||
* to the same value.
|
||||
*/
|
||||
process_implied_equality(root,
|
||||
leftop,
|
||||
rightop,
|
||||
rinfo->left_sortop,
|
||||
rinfo->right_sortop,
|
||||
rinfo->left_relids,
|
||||
rinfo->right_relids,
|
||||
true);
|
||||
|
||||
/* No need to match against remaining set members */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Examine each mergejoinable outer-join clause with OUTERVAR on right */
|
||||
foreach(l1, root->right_join_clauses)
|
||||
{
|
||||
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l1);
|
||||
Node *leftop = get_leftop(rinfo->clause);
|
||||
Node *rightop = get_rightop(rinfo->clause);
|
||||
ListCell *l2;
|
||||
|
||||
/* Scan to see if it matches any element of equi_key_set */
|
||||
foreach(l2, equi_key_set)
|
||||
{
|
||||
PathKeyItem *item1 = (PathKeyItem *) lfirst(l2);
|
||||
|
||||
if (equal(rightop, item1->key) &&
|
||||
rinfo->right_sortop == item1->sortop)
|
||||
{
|
||||
/*
|
||||
* Yes, so find constant member(s) of set and generate
|
||||
* implied INNERVAR = CONSTANT
|
||||
*/
|
||||
process_implied_const_eq(root, equi_key_set, relids,
|
||||
leftop,
|
||||
rinfo->left_sortop,
|
||||
rinfo->left_relids,
|
||||
false);
|
||||
/*
|
||||
* We can remove the explicit outer join qual, too,
|
||||
* since we now have tests forcing each of its sides
|
||||
* to the same value.
|
||||
*/
|
||||
process_implied_equality(root,
|
||||
leftop,
|
||||
rightop,
|
||||
rinfo->left_sortop,
|
||||
rinfo->right_sortop,
|
||||
rinfo->left_relids,
|
||||
rinfo->right_relids,
|
||||
true);
|
||||
|
||||
/* No need to match against remaining set members */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Examine each mergejoinable full-join clause */
|
||||
foreach(l1, root->full_join_clauses)
|
||||
{
|
||||
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l1);
|
||||
Node *leftop = get_leftop(rinfo->clause);
|
||||
Node *rightop = get_rightop(rinfo->clause);
|
||||
int i1 = 0;
|
||||
ListCell *l2;
|
||||
|
||||
/* Scan to see if it matches any element of equi_key_set */
|
||||
foreach(l2, equi_key_set)
|
||||
{
|
||||
PathKeyItem *item1 = (PathKeyItem *) lfirst(l2);
|
||||
CoalesceExpr *cexpr = (CoalesceExpr *) item1->key;
|
||||
|
||||
/*
|
||||
* Try to match a pathkey containing a COALESCE() expression
|
||||
* to the join clause. We can assume the COALESCE() inputs
|
||||
* are in the same order as the join clause, since both were
|
||||
* automatically generated in the cases we care about.
|
||||
*
|
||||
* XXX currently this may fail to match in cross-type cases
|
||||
* because the COALESCE will contain typecast operations while
|
||||
* the join clause may not (if there is a cross-type mergejoin
|
||||
* operator available for the two column types).
|
||||
* Is it OK to strip implicit coercions from the COALESCE
|
||||
* arguments? What of the sortops in such cases?
|
||||
*/
|
||||
if (IsA(cexpr, CoalesceExpr) &&
|
||||
list_length(cexpr->args) == 2 &&
|
||||
equal(leftop, (Node *) linitial(cexpr->args)) &&
|
||||
equal(rightop, (Node *) lsecond(cexpr->args)) &&
|
||||
rinfo->left_sortop == item1->sortop &&
|
||||
rinfo->right_sortop == item1->sortop)
|
||||
{
|
||||
/*
|
||||
* Yes, so find constant member(s) of set and generate
|
||||
* implied LEFTVAR = CONSTANT
|
||||
*/
|
||||
process_implied_const_eq(root, equi_key_set, relids,
|
||||
leftop,
|
||||
rinfo->left_sortop,
|
||||
rinfo->left_relids,
|
||||
false);
|
||||
/* ... and RIGHTVAR = CONSTANT */
|
||||
process_implied_const_eq(root, equi_key_set, relids,
|
||||
rightop,
|
||||
rinfo->right_sortop,
|
||||
rinfo->right_relids,
|
||||
false);
|
||||
/* ... and remove COALESCE() = CONSTANT */
|
||||
process_implied_const_eq(root, equi_key_set, relids,
|
||||
item1->key,
|
||||
item1->sortop,
|
||||
relids[i1],
|
||||
true);
|
||||
/*
|
||||
* We can remove the explicit outer join qual, too,
|
||||
* since we now have tests forcing each of its sides
|
||||
* to the same value.
|
||||
*/
|
||||
process_implied_equality(root,
|
||||
leftop,
|
||||
rightop,
|
||||
rinfo->left_sortop,
|
||||
rinfo->right_sortop,
|
||||
rinfo->left_relids,
|
||||
rinfo->right_relids,
|
||||
true);
|
||||
|
||||
/* No need to match against remaining set members */
|
||||
break;
|
||||
}
|
||||
i1++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* process_implied_const_eq
|
||||
* Apply process_implied_equality with the given item and each
|
||||
* pseudoconstant member of equi_key_set.
|
||||
*
|
||||
* This is just a subroutine to save some cruft in
|
||||
* generate_outer_join_implications. equi_key_set and relids are as in
|
||||
* generate_outer_join_implications, the other parameters as for
|
||||
* process_implied_equality.
|
||||
*/
|
||||
static void
|
||||
process_implied_const_eq(PlannerInfo *root, List *equi_key_set, Relids *relids,
|
||||
Node *item1, Oid sortop1, Relids item1_relids,
|
||||
bool delete_it)
|
||||
{
|
||||
ListCell *l;
|
||||
bool found = false;
|
||||
int i = 0;
|
||||
|
||||
foreach(l, equi_key_set)
|
||||
{
|
||||
PathKeyItem *item2 = (PathKeyItem *) lfirst(l);
|
||||
|
||||
if (bms_is_empty(relids[i]))
|
||||
{
|
||||
process_implied_equality(root,
|
||||
item1, item2->key,
|
||||
sortop1, item2->sortop,
|
||||
item1_relids, NULL,
|
||||
delete_it);
|
||||
found = true;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
/* Caller screwed up if no constants in list */
|
||||
Assert(found);
|
||||
}
|
||||
|
||||
/*
|
||||
* exprs_known_equal
|
||||
* Detect whether two expressions are known equal due to equijoin clauses.
|
||||
|
Reference in New Issue
Block a user