aboutsummaryrefslogtreecommitdiff
path: root/src/backend/optimizer/path/pathkeys.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/optimizer/path/pathkeys.c')
-rw-r--r--src/backend/optimizer/path/pathkeys.c291
1 files changed, 288 insertions, 3 deletions
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index 52075fbf465..389d89f0d8d 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -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;
/*
@@ -238,6 +251,20 @@ generate_implied_equalities(PlannerInfo *root)
}
/*
+ * 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).
*/
@@ -286,6 +313,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.
*