From: Tom Lane Date: Sat, 2 Jul 2005 23:00:42 +0000 (+0000) Subject: Teach planner about some cases where a restriction clause can be X-Git-Tag: REL8_1_0BETA1~406 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=cc5e80b8d1c4bf86aa99b54c938e9048e10bf93a;p=postgresql 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. --- diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index ce16d2eba1..8d42cead08 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.310 2005/06/28 05:08:56 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.311 2005/07/02 23:00:39 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1249,7 +1249,6 @@ _copyRestrictInfo(RestrictInfo *from) COPY_NODE_FIELD(clause); COPY_SCALAR_FIELD(is_pushed_down); - COPY_SCALAR_FIELD(valid_everywhere); COPY_SCALAR_FIELD(can_join); COPY_BITMAPSET_FIELD(clause_relids); COPY_BITMAPSET_FIELD(required_relids); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index ade4f16a09..e8c8a0cbc9 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -18,7 +18,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.247 2005/06/28 05:08:57 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.248 2005/07/02 23:00:39 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -603,7 +603,6 @@ _equalRestrictInfo(RestrictInfo *a, RestrictInfo *b) { COMPARE_NODE_FIELD(clause); COMPARE_SCALAR_FIELD(is_pushed_down); - COMPARE_SCALAR_FIELD(valid_everywhere); COMPARE_BITMAPSET_FIELD(required_relids); /* diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 81fb9c88fa..0b905dd043 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.257 2005/06/28 05:08:57 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.258 2005/07/02 23:00:39 tgl Exp $ * * NOTES * Every node type that can appear in stored rules' parsetrees *must* @@ -1164,9 +1164,13 @@ _outPlannerInfo(StringInfo str, PlannerInfo *node) WRITE_NODE_FIELD(parse); WRITE_NODE_FIELD(join_rel_list); WRITE_NODE_FIELD(equi_key_list); + WRITE_NODE_FIELD(left_join_clauses); + WRITE_NODE_FIELD(right_join_clauses); + WRITE_NODE_FIELD(full_join_clauses); WRITE_NODE_FIELD(in_info_list); WRITE_NODE_FIELD(query_pathkeys); WRITE_BOOL_FIELD(hasJoinRTEs); + WRITE_BOOL_FIELD(hasOuterJoins); WRITE_BOOL_FIELD(hasHavingQual); } @@ -1234,7 +1238,6 @@ _outRestrictInfo(StringInfo str, RestrictInfo *node) /* NB: this isn't a complete set of fields */ WRITE_NODE_FIELD(clause); WRITE_BOOL_FIELD(is_pushed_down); - WRITE_BOOL_FIELD(valid_everywhere); WRITE_BOOL_FIELD(can_join); WRITE_BITMAPSET_FIELD(clause_relids); WRITE_BITMAPSET_FIELD(required_relids); diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index be68409411..07b67a8822 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.185 2005/06/14 04:04:30 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.186 2005/07/02 23:00:40 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1882,7 +1882,7 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups) { resultquals = lappend(resultquals, make_restrictinfo(boolqual, - true, true, + true, NULL)); continue; } @@ -2132,7 +2132,7 @@ prefix_quals(Node *leftop, Oid opclass, elog(ERROR, "no = operator for opclass %u", opclass); expr = make_opclause(oproid, BOOLOID, false, (Expr *) leftop, (Expr *) prefix_const); - result = list_make1(make_restrictinfo(expr, true, true, NULL)); + result = list_make1(make_restrictinfo(expr, true, NULL)); return result; } @@ -2147,7 +2147,7 @@ prefix_quals(Node *leftop, Oid opclass, elog(ERROR, "no >= operator for opclass %u", opclass); expr = make_opclause(oproid, BOOLOID, false, (Expr *) leftop, (Expr *) prefix_const); - result = list_make1(make_restrictinfo(expr, true, true, NULL)); + result = list_make1(make_restrictinfo(expr, true, NULL)); /*------- * If we can create a string larger than the prefix, we can say @@ -2163,7 +2163,7 @@ prefix_quals(Node *leftop, Oid opclass, elog(ERROR, "no < operator for opclass %u", opclass); expr = make_opclause(oproid, BOOLOID, false, (Expr *) leftop, (Expr *) greaterstr); - result = lappend(result, make_restrictinfo(expr, true, true, NULL)); + result = lappend(result, make_restrictinfo(expr, true, NULL)); } return result; @@ -2234,7 +2234,7 @@ network_prefix_quals(Node *leftop, Oid expr_op, Oid opclass, Datum rightop) (Expr *) leftop, (Expr *) makeConst(datatype, -1, opr1right, false, false)); - result = list_make1(make_restrictinfo(expr, true, true, NULL)); + result = list_make1(make_restrictinfo(expr, true, NULL)); /* create clause "key <= network_scan_last( rightop )" */ @@ -2249,7 +2249,7 @@ network_prefix_quals(Node *leftop, Oid expr_op, Oid opclass, Datum rightop) (Expr *) leftop, (Expr *) makeConst(datatype, -1, opr2right, false, false)); - result = lappend(result, make_restrictinfo(expr, true, true, NULL)); + result = lappend(result, make_restrictinfo(expr, true, NULL)); return result; } diff --git a/src/backend/optimizer/path/orindxpath.c b/src/backend/optimizer/path/orindxpath.c index 7eadd220b9..884329edd9 100644 --- a/src/backend/optimizer/path/orindxpath.c +++ b/src/backend/optimizer/path/orindxpath.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/orindxpath.c,v 1.72 2005/06/09 04:18:59 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/path/orindxpath.c,v 1.73 2005/07/02 23:00:40 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -90,16 +90,13 @@ create_or_index_quals(PlannerInfo *root, RelOptInfo *rel) ListCell *i; /* - * Find potentially interesting OR joinclauses. We must ignore any - * joinclauses that are not marked valid_everywhere, because they - * cannot be pushed down due to outer-join rules. + * Find potentially interesting OR joinclauses. */ foreach(i, rel->joininfo) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(i); - if (restriction_is_or_clause(rinfo) && - rinfo->valid_everywhere) + if (restriction_is_or_clause(rinfo)) { /* * Use the generate_bitmap_or_paths() machinery to estimate @@ -140,8 +137,7 @@ create_or_index_quals(PlannerInfo *root, RelOptInfo *rel) * Convert the path's indexclauses structure to a RestrictInfo tree, * and add it to the rel's restriction list. */ - newrinfos = make_restrictinfo_from_bitmapqual((Path *) bestpath, - true, true); + newrinfos = make_restrictinfo_from_bitmapqual((Path *) bestpath, true); Assert(list_length(newrinfos) == 1); or_rinfo = (RestrictInfo *) linitial(newrinfos); Assert(IsA(or_rinfo, RestrictInfo)); diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c index 52075fbf46..389d89f0d8 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; /* @@ -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. diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 76812e2e4b..959c17206c 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.192 2005/06/10 22:25:36 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.193 2005/07/02 23:00:41 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1176,8 +1176,7 @@ create_nestloop_plan(PlannerInfo *root, List *bitmapclauses; bitmapclauses = - make_restrictinfo_from_bitmapqual(innerpath->bitmapqual, - true, true); + make_restrictinfo_from_bitmapqual(innerpath->bitmapqual, true); joinrestrictclauses = select_nonredundant_join_clauses(root, joinrestrictclauses, diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index f7066e4906..91a23d6d1c 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.107 2005/06/09 04:18:59 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.108 2005/07/02 23:00:41 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -374,8 +374,8 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, Relids qualscope) { Relids relids; - bool valid_everywhere; - bool can_be_equijoin; + bool maybe_equijoin; + bool maybe_outer_join; RestrictInfo *restrictinfo; RelOptInfo *rel; List *vars; @@ -409,14 +409,15 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, if (isdeduced) { /* - * If the qual came from implied-equality deduction, we can - * evaluate the qual at its natural semantic level. It is not - * affected by any outer-join rules (else we'd not have decided - * the vars were equal). + * 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. */ Assert(bms_equal(relids, qualscope)); - valid_everywhere = true; - can_be_equijoin = true; + /* Needn't feed it back for more deductions */ + maybe_equijoin = false; + maybe_outer_join = false; } else if (bms_overlap(relids, outerjoin_nonnullable)) { @@ -434,8 +435,14 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, * result, so we treat it the same as an ordinary inner-join qual. */ relids = qualscope; - valid_everywhere = false; - can_be_equijoin = false; + /* + * 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. + */ + maybe_equijoin = false; + maybe_outer_join = true; } else { @@ -449,34 +456,25 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, * time we are called, the outerjoinset of each baserel will show * exactly those outer joins that are below the qual in the join * tree. - * - * We also need to determine whether the qual is "valid everywhere", - * which is true if the qual mentions no variables that are - * involved in lower-level outer joins (this may be an overly - * strong test). */ Relids addrelids = NULL; Relids tmprelids; int relno; - valid_everywhere = true; tmprelids = bms_copy(relids); while ((relno = bms_first_member(tmprelids)) >= 0) { RelOptInfo *rel = find_base_rel(root, relno); if (rel->outerjoinset != NULL) - { addrelids = bms_add_members(addrelids, rel->outerjoinset); - valid_everywhere = false; - } } bms_free(tmprelids); if (bms_is_subset(addrelids, relids)) { /* Qual is not affected by any outer-join restriction */ - can_be_equijoin = true; + maybe_equijoin = true; } else { @@ -488,9 +486,10 @@ 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. */ - can_be_equijoin = false; + maybe_equijoin = false; } bms_free(addrelids); + maybe_outer_join = false; } /* @@ -508,7 +507,6 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, */ restrictinfo = make_restrictinfo((Expr *) clause, is_pushed_down, - valid_everywhere, relids); /* @@ -533,8 +531,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, * allows us to consider z and q equal after their rels are * joined. */ - if (can_be_equijoin) - check_mergejoinable(restrictinfo); + check_mergejoinable(restrictinfo); /* * If the clause was deduced from implied equality, check to @@ -601,18 +598,60 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, } /* - * If the clause has a mergejoinable operator, and 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). Record the key - * equivalence for future use. (We can skip this for a deduced - * clause, since the keys are already known equivalent in that case.) + * If the clause has a mergejoinable operator, we may be able to + * deduce more things from it under the principle of transitivity. + * + * 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 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.) */ - if (can_be_equijoin && - restrictinfo->mergejoinoperator != InvalidOid && - !isdeduced) - add_equijoined_keys(root, restrictinfo); + if (restrictinfo->mergejoinoperator != InvalidOid) + { + if (maybe_equijoin) + add_equijoined_keys(root, restrictinfo); + else if (maybe_outer_join && restrictinfo->can_join) + { + if (bms_is_subset(restrictinfo->left_relids, + outerjoin_nonnullable) && + !bms_overlap(restrictinfo->right_relids, + outerjoin_nonnullable)) + { + /* we have outervar = innervar */ + root->left_join_clauses = lappend(root->left_join_clauses, + restrictinfo); + } + else if (bms_is_subset(restrictinfo->right_relids, + outerjoin_nonnullable) && + !bms_overlap(restrictinfo->left_relids, + outerjoin_nonnullable)) + { + /* we have innervar = outervar */ + root->right_join_clauses = lappend(root->right_join_clauses, + restrictinfo); + } + else 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); + } + } + } } /* diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 788fd2f324..7038a45ac6 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -14,7 +14,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/planmain.c,v 1.85 2005/06/10 03:32:23 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/planmain.c,v 1.86 2005/07/02 23:00:41 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -121,6 +121,9 @@ query_planner(PlannerInfo *root, List *tlist, double tuple_fraction, root->join_rel_list = NIL; root->join_rel_hash = NULL; root->equi_key_list = NIL; + root->left_join_clauses = NIL; + root->right_join_clauses = NIL; + root->full_join_clauses = NIL; /* * Construct RelOptInfo nodes for all base relations in query. diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index df8d0556b4..334f8504df 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.189 2005/06/10 02:21:04 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.190 2005/07/02 23:00:41 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -194,7 +194,6 @@ subquery_planner(Query *parse, double tuple_fraction, int saved_planid = PlannerPlanId; PlannerInfo *root; Plan *plan; - bool hasOuterJoins; List *newHaving; List *lst; ListCell *l; @@ -228,12 +227,16 @@ subquery_planner(Query *parse, double tuple_fraction, /* * Detect whether any rangetable entries are RTE_JOIN kind; if not, we * can avoid the expense of doing flatten_join_alias_vars(). Also - * check for outer joins --- if none, we can skip - * reduce_outer_joins(). This must be done after we have done + * check for outer joins --- if none, we can skip reduce_outer_joins() + * and some other processing. This must be done after we have done * pull_up_subqueries, of course. + * + * Note: if reduce_outer_joins manages to eliminate all outer joins, + * root->hasOuterJoins is not reset currently. This is OK since its + * purpose is merely to suppress unnecessary processing in simple cases. */ root->hasJoinRTEs = false; - hasOuterJoins = false; + root->hasOuterJoins = false; foreach(l, parse->rtable) { RangeTblEntry *rte = (RangeTblEntry *) lfirst(l); @@ -243,7 +246,7 @@ subquery_planner(Query *parse, double tuple_fraction, root->hasJoinRTEs = true; if (IS_OUTER_JOIN(rte->jointype)) { - hasOuterJoins = true; + root->hasOuterJoins = true; /* Can quit scanning once we find an outer join */ break; } @@ -347,7 +350,7 @@ subquery_planner(Query *parse, double tuple_fraction, * joins. This step is most easily done after we've done expression * preprocessing. */ - if (hasOuterJoins) + if (root->hasOuterJoins) reduce_outer_joins(root); /* diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c index a76f1bb7a2..ba3d82dd30 100644 --- a/src/backend/optimizer/util/restrictinfo.c +++ b/src/backend/optimizer/util/restrictinfo.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/restrictinfo.c,v 1.37 2005/06/09 04:19:00 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/restrictinfo.c,v 1.38 2005/07/02 23:00:41 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -24,11 +24,9 @@ static RestrictInfo *make_restrictinfo_internal(Expr *clause, Expr *orclause, bool is_pushed_down, - bool valid_everywhere, Relids required_relids); static Expr *make_sub_restrictinfos(Expr *clause, - bool is_pushed_down, - bool valid_everywhere); + bool is_pushed_down); static RestrictInfo *join_clause_is_redundant(PlannerInfo *root, RestrictInfo *rinfo, List *reference_list, @@ -40,8 +38,8 @@ static RestrictInfo *join_clause_is_redundant(PlannerInfo *root, * * Build a RestrictInfo node containing the given subexpression. * - * The is_pushed_down and valid_everywhere flags must be supplied by the - * caller. required_relids can be NULL, in which case it defaults to the + * The is_pushed_down flag must be supplied by the caller. + * required_relids can be NULL, in which case it defaults to the * actual clause contents (i.e., clause_relids). * * We initialize fields that depend only on the given subexpression, leaving @@ -49,23 +47,19 @@ static RestrictInfo *join_clause_is_redundant(PlannerInfo *root, * later. */ RestrictInfo * -make_restrictinfo(Expr *clause, bool is_pushed_down, bool valid_everywhere, - Relids required_relids) +make_restrictinfo(Expr *clause, bool is_pushed_down, Relids required_relids) { /* * If it's an OR clause, build a modified copy with RestrictInfos * inserted above each subclause of the top-level AND/OR structure. */ if (or_clause((Node *) clause)) - return (RestrictInfo *) make_sub_restrictinfos(clause, - is_pushed_down, - valid_everywhere); + return (RestrictInfo *) make_sub_restrictinfos(clause, is_pushed_down); /* Shouldn't be an AND clause, else AND/OR flattening messed up */ Assert(!and_clause((Node *) clause)); - return make_restrictinfo_internal(clause, NULL, - is_pushed_down, valid_everywhere, + return make_restrictinfo_internal(clause, NULL, is_pushed_down, required_relids); } @@ -84,9 +78,7 @@ make_restrictinfo(Expr *clause, bool is_pushed_down, bool valid_everywhere, * a specialized routine to allow sharing of RestrictInfos. */ List * -make_restrictinfo_from_bitmapqual(Path *bitmapqual, - bool is_pushed_down, - bool valid_everywhere) +make_restrictinfo_from_bitmapqual(Path *bitmapqual, bool is_pushed_down) { List *result; @@ -101,8 +93,7 @@ make_restrictinfo_from_bitmapqual(Path *bitmapqual, List *sublist; sublist = make_restrictinfo_from_bitmapqual((Path *) lfirst(l), - is_pushed_down, - valid_everywhere); + is_pushed_down); result = list_concat(result, sublist); } } @@ -118,8 +109,7 @@ make_restrictinfo_from_bitmapqual(Path *bitmapqual, List *sublist; sublist = make_restrictinfo_from_bitmapqual((Path *) lfirst(l), - is_pushed_down, - valid_everywhere); + is_pushed_down); if (sublist == NIL) { /* constant TRUE input yields constant TRUE OR result */ @@ -137,7 +127,6 @@ make_restrictinfo_from_bitmapqual(Path *bitmapqual, list_make1(make_restrictinfo_internal(make_orclause(withoutris), make_orclause(withris), is_pushed_down, - valid_everywhere, NULL)); } else if (IsA(bitmapqual, IndexPath)) @@ -162,15 +151,13 @@ make_restrictinfo_from_bitmapqual(Path *bitmapqual, */ static RestrictInfo * make_restrictinfo_internal(Expr *clause, Expr *orclause, - bool is_pushed_down, bool valid_everywhere, - Relids required_relids) + bool is_pushed_down, Relids required_relids) { RestrictInfo *restrictinfo = makeNode(RestrictInfo); restrictinfo->clause = clause; restrictinfo->orclause = orclause; restrictinfo->is_pushed_down = is_pushed_down; - restrictinfo->valid_everywhere = valid_everywhere; restrictinfo->can_join = false; /* may get set below */ /* @@ -250,8 +237,7 @@ make_restrictinfo_internal(Expr *clause, Expr *orclause, * simple clauses are valid RestrictInfos. */ static Expr * -make_sub_restrictinfos(Expr *clause, bool is_pushed_down, - bool valid_everywhere) +make_sub_restrictinfos(Expr *clause, bool is_pushed_down) { if (or_clause((Node *) clause)) { @@ -261,12 +247,10 @@ make_sub_restrictinfos(Expr *clause, bool is_pushed_down, foreach(temp, ((BoolExpr *) clause)->args) orlist = lappend(orlist, make_sub_restrictinfos(lfirst(temp), - is_pushed_down, - valid_everywhere)); + is_pushed_down)); return (Expr *) make_restrictinfo_internal(clause, make_orclause(orlist), is_pushed_down, - valid_everywhere, NULL); } else if (and_clause((Node *) clause)) @@ -277,15 +261,13 @@ make_sub_restrictinfos(Expr *clause, bool is_pushed_down, foreach(temp, ((BoolExpr *) clause)->args) andlist = lappend(andlist, make_sub_restrictinfos(lfirst(temp), - is_pushed_down, - valid_everywhere)); + is_pushed_down)); return make_andclause(andlist); } else return (Expr *) make_restrictinfo_internal(clause, NULL, is_pushed_down, - valid_everywhere, NULL); } diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 0a2ca0e5f3..2f906c6a47 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.115 2005/06/13 23:14:49 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.116 2005/07/02 23:00:42 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -87,6 +87,15 @@ typedef struct PlannerInfo List *equi_key_list; /* list of lists of equijoined * PathKeyItems */ + List *left_join_clauses; /* list of RestrictInfos for outer join + * clauses w/nonnullable var on left */ + + List *right_join_clauses; /* list of RestrictInfos for outer join + * clauses w/nonnullable var on right */ + + List *full_join_clauses; /* list of RestrictInfos for full outer + * join clauses */ + List *in_info_list; /* list of InClauseInfos */ List *query_pathkeys; /* desired pathkeys for query_planner(), @@ -95,6 +104,7 @@ typedef struct PlannerInfo double tuple_fraction; /* tuple_fraction passed to query_planner */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ + bool hasOuterJoins; /* true if any RTEs are outer joins */ bool hasHavingQual; /* true if havingQual was non-null */ } PlannerInfo; @@ -695,10 +705,6 @@ typedef struct HashPath * joined, will also have is_pushed_down set because it will get attached to * some lower joinrel. * - * We also store a valid_everywhere flag, which says that the clause is not - * affected by any lower-level outer join, and therefore any conditions it - * asserts can be presumed true throughout the plan tree. - * * In general, the referenced clause might be arbitrarily complex. The * kinds of clauses we can handle as indexscan quals, mergejoin clauses, * or hashjoin clauses are fairly limited --- the code for each kind of @@ -725,8 +731,6 @@ typedef struct RestrictInfo bool is_pushed_down; /* TRUE if clause was pushed down in level */ - bool valid_everywhere; /* TRUE if valid on every level */ - /* * This flag is set true if the clause looks potentially useful as a * merge or hash join clause, that is if it is a binary opclause with diff --git a/src/include/optimizer/restrictinfo.h b/src/include/optimizer/restrictinfo.h index 20f51c5820..5a9c2f2722 100644 --- a/src/include/optimizer/restrictinfo.h +++ b/src/include/optimizer/restrictinfo.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/optimizer/restrictinfo.h,v 1.31 2005/06/09 04:19:00 tgl Exp $ + * $PostgreSQL: pgsql/src/include/optimizer/restrictinfo.h,v 1.32 2005/07/02 23:00:42 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -19,11 +19,9 @@ extern RestrictInfo *make_restrictinfo(Expr *clause, bool is_pushed_down, - bool valid_everywhere, Relids required_relids); extern List *make_restrictinfo_from_bitmapqual(Path *bitmapqual, - bool is_pushed_down, - bool valid_everywhere); + bool is_pushed_down); extern bool restriction_is_or_clause(RestrictInfo *restrictinfo); extern List *get_actual_clauses(List *restrictinfo_list); extern void get_actual_join_clauses(List *restrictinfo_list,