diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/nodes/copyfuncs.c | 33 | ||||
-rw-r--r-- | src/backend/nodes/equalfuncs.c | 31 | ||||
-rw-r--r-- | src/backend/nodes/list.c | 42 | ||||
-rw-r--r-- | src/backend/nodes/outfuncs.c | 10 | ||||
-rw-r--r-- | src/backend/nodes/readfuncs.c | 9 | ||||
-rw-r--r-- | src/backend/optimizer/path/indxpath.c | 776 | ||||
-rw-r--r-- | src/backend/optimizer/path/joinpath.c | 71 | ||||
-rw-r--r-- | src/backend/optimizer/path/orindxpath.c | 5 | ||||
-rw-r--r-- | src/backend/optimizer/path/tidpath.c | 42 | ||||
-rw-r--r-- | src/backend/optimizer/plan/initsplan.c | 15 | ||||
-rw-r--r-- | src/backend/optimizer/util/pathnode.c | 9 | ||||
-rw-r--r-- | src/backend/optimizer/util/plancat.c | 6 | ||||
-rw-r--r-- | src/backend/optimizer/util/relnode.c | 66 | ||||
-rw-r--r-- | src/backend/optimizer/util/restrictinfo.c | 87 | ||||
-rw-r--r-- | src/include/nodes/nodes.h | 3 | ||||
-rw-r--r-- | src/include/nodes/pg_list.h | 3 | ||||
-rw-r--r-- | src/include/nodes/relation.h | 104 | ||||
-rw-r--r-- | src/include/optimizer/paths.h | 4 | ||||
-rw-r--r-- | src/include/optimizer/restrictinfo.h | 5 |
19 files changed, 742 insertions, 579 deletions
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index eb9bbe93658..da3bf7b7213 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 - * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.220 2002/11/23 03:59:07 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.221 2002/11/24 21:52:13 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1148,7 +1148,9 @@ _copyRelOptInfo(RelOptInfo *from) newnode->baserestrictcost = from->baserestrictcost; newnode->outerjoinset = listCopy(from->outerjoinset); Node_Copy(from, newnode, joininfo); - Node_Copy(from, newnode, innerjoin); + + newnode->index_outer_relids = listCopy(from->index_outer_relids); + Node_Copy(from, newnode, index_inner_paths); return newnode; } @@ -1200,6 +1202,9 @@ _copyIndexOptInfo(IndexOptInfo *from) Node_Copy(from, newnode, indpred); newnode->unique = from->unique; + newnode->outer_relids = listCopy(from->outer_relids); + Node_Copy(from, newnode, inner_paths); + return newnode; } @@ -1262,8 +1267,6 @@ _copyIndexPath(IndexPath *from) Node_Copy(from, newnode, indexinfo); Node_Copy(from, newnode, indexqual); newnode->indexscandir = from->indexscandir; - newnode->joinrelids = listCopy(from->joinrelids); - newnode->alljoinquals = from->alljoinquals; newnode->rows = from->rows; return newnode; @@ -1491,6 +1494,25 @@ _copyJoinInfo(JoinInfo *from) return newnode; } +/* ---------------- + * _copyInnerIndexscanInfo + * ---------------- + */ +static InnerIndexscanInfo * +_copyInnerIndexscanInfo(InnerIndexscanInfo *from) +{ + InnerIndexscanInfo *newnode = makeNode(InnerIndexscanInfo); + + /* + * copy remainder of node + */ + newnode->other_relids = listCopy(from->other_relids); + newnode->isouterjoin = from->isouterjoin; + Node_Copy(from, newnode, best_innerpath); + + return newnode; +} + /* **************************************************************** * parsenodes.h copy functions * **************************************************************** @@ -2952,6 +2974,9 @@ copyObject(void *from) case T_IndexOptInfo: retval = _copyIndexOptInfo(from); break; + case T_InnerIndexscanInfo: + retval = _copyInnerIndexscanInfo(from); + break; /* * VALUE NODES diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 12781797c3d..3e0ea75d251 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -20,7 +20,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.166 2002/11/23 03:59:07 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.167 2002/11/24 21:52:13 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -429,10 +429,6 @@ _equalIndexPath(IndexPath *a, IndexPath *b) return false; if (a->indexscandir != b->indexscandir) return false; - if (!equali(a->joinrelids, b->joinrelids)) - return false; - if (a->alljoinquals != b->alljoinquals) - return false; /* * Skip 'rows' because of possibility of floating-point roundoff @@ -548,13 +544,11 @@ _equalRestrictInfo(RestrictInfo *a, RestrictInfo *b) return false; /* - * We ignore eval_cost, this_selec, left/right_pathkey, and - * left/right_bucketsize, since they may not be set yet, and should be - * derivable from the clause anyway. Probably it's not really - * necessary to compare any of these remaining fields ... + * We ignore subclauseindices, eval_cost, this_selec, left/right_pathkey, + * and left/right_bucketsize, since they may not be set yet, and should be + * derivable from the clause anyway. Probably it's not really necessary + * to compare any of these remaining fields ... */ - if (!equal(a->subclauseindices, b->subclauseindices)) - return false; if (a->mergejoinoperator != b->mergejoinoperator) return false; if (a->left_sortop != b->left_sortop) @@ -576,6 +570,18 @@ _equalJoinInfo(JoinInfo *a, JoinInfo *b) return true; } +static bool +_equalInnerIndexscanInfo(InnerIndexscanInfo *a, InnerIndexscanInfo *b) +{ + if (!equali(a->other_relids, b->other_relids)) + return false; + if (a->isouterjoin != b->isouterjoin) + return false; + if (!equal(a->best_innerpath, b->best_innerpath)) + return false; + return true; +} + /* * Stuff from parsenodes.h */ @@ -2120,6 +2126,9 @@ equal(void *a, void *b) case T_JoinInfo: retval = _equalJoinInfo(a, b); break; + case T_InnerIndexscanInfo: + retval = _equalInnerIndexscanInfo(a, b); + break; case T_TidPath: retval = _equalTidPath(a, b); break; diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c index 063212e25fa..6dc6001a0f2 100644 --- a/src/backend/nodes/list.c +++ b/src/backend/nodes/list.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/list.c,v 1.41 2002/06/20 20:29:29 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/list.c,v 1.42 2002/11/24 21:52:13 tgl Exp $ * * NOTES * XXX a few of the following functions are duplicated to handle @@ -373,6 +373,46 @@ set_unioni(List *l1, List *l2) } /* + * Generate the intersection of two lists, + * ie, all members of both l1 and l2. + * + * NOTE: if there are duplicates in l1 they will still be duplicate in the + * result; but duplicates in l2 are discarded. + * + * The result is a fresh List, but it points to the same member nodes + * as were in the inputs. + */ +#ifdef NOT_USED +List * +set_intersect(List *l1, List *l2) +{ + List *retval = NIL; + List *i; + + foreach(i, l1) + { + if (member(lfirst(i), l2)) + retval = lappend(retval, lfirst(i)); + } + return retval; +} +#endif + +List * +set_intersecti(List *l1, List *l2) +{ + List *retval = NIL; + List *i; + + foreach(i, l1) + { + if (intMember(lfirsti(i), l2)) + retval = lappendi(retval, lfirsti(i)); + } + return retval; +} + +/* * member() * nondestructive, returns t iff l1 is a member of the list l2 */ diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 2d1f2236c9b..8df0783fb89 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -5,7 +5,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.180 2002/11/15 02:50:07 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.181 2002/11/24 21:52:13 tgl Exp $ * * NOTES * Every (plan) node in POSTGRES has an associated "out" routine which @@ -1067,12 +1067,8 @@ _outIndexPath(StringInfo str, IndexPath *node) appendStringInfo(str, " :indexqual "); _outNode(str, node->indexqual); - appendStringInfo(str, " :indexscandir %d :joinrelids ", - (int) node->indexscandir); - _outIntList(str, node->joinrelids); - - appendStringInfo(str, " :alljoinquals %s :rows %.2f ", - booltostr(node->alljoinquals), + appendStringInfo(str, " :indexscandir %d :rows %.2f ", + (int) node->indexscandir, node->rows); } diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 9b2198ec5a5..43fe5bbd35f 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.137 2002/11/15 02:50:07 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.138 2002/11/24 21:52:13 tgl Exp $ * * NOTES * Most of the read functions for plan nodes are tested. (In fact, they @@ -1824,13 +1824,6 @@ _readIndexPath(void) token = pg_strtok(&length); /* now read it */ local_node->indexscandir = (ScanDirection) atoi(token); - token = pg_strtok(&length); /* get :joinrelids */ - local_node->joinrelids = toIntList(nodeRead(true)); - - token = pg_strtok(&length); /* get :alljoinquals */ - token = pg_strtok(&length); /* now read it */ - local_node->alljoinquals = strtobool(token); - token = pg_strtok(&length); /* get :rows */ token = pg_strtok(&length); /* now read it */ local_node->rows = atof(token); diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 2dab5e54cdd..ae5db3634ff 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.124 2002/11/01 19:33:09 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.125 2002/11/24 21:52:14 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -46,15 +46,11 @@ /* * DoneMatchingIndexKeys() - MACRO * - * Determine whether we should continue matching index keys in a clause. - * Depends on if there are more to match or if this is a functional index. - * In the latter case we stop after the first match since there can - * be only 1 key (i.e. the function's return value) and the attributes in - * keys list represent the arguments to the function. -mer 3 Oct. 1991 + * Formerly this looked at indexkeys, but that's the wrong thing for a + * functional index. */ -#define DoneMatchingIndexKeys(indexkeys, index) \ - (indexkeys[0] == 0 || \ - (index->indproc != InvalidOid)) +#define DoneMatchingIndexKeys(indexkeys, classes) \ + (classes[0] == InvalidOid) #define is_indexable_operator(clause,opclass,indexkey_on_left) \ (indexable_operator(clause,opclass,indexkey_on_left) != InvalidOid) @@ -68,28 +64,25 @@ static List *match_index_orclause(RelOptInfo *rel, IndexOptInfo *index, static bool match_or_subclause_to_indexkey(RelOptInfo *rel, IndexOptInfo *index, Expr *clause); -static List *group_clauses_by_indexkey(RelOptInfo *rel, IndexOptInfo *index, - int *indexkeys, Oid *classes, - List *restrictinfo_list); -static List *group_clauses_by_ikey_for_joins(RelOptInfo *rel, +static List *group_clauses_by_indexkey(RelOptInfo *rel, IndexOptInfo *index); +static List *group_clauses_by_indexkey_for_join(RelOptInfo *rel, IndexOptInfo *index, - int *indexkeys, Oid *classes, - List *join_cinfo_list, - List *restr_cinfo_list); + Relids outer_relids, + bool isouterjoin); static bool match_clause_to_indexkey(RelOptInfo *rel, IndexOptInfo *index, - int indexkey, Oid opclass, - Expr *clause, bool join); + int indexkey, Oid opclass, Expr *clause); +static bool match_join_clause_to_indexkey(RelOptInfo *rel, IndexOptInfo *index, + int indexkey, Oid opclass, Expr *clause); static bool pred_test(List *predicate_list, List *restrictinfo_list, List *joininfo_list, int relvarno); static bool pred_test_restrict_list(Expr *predicate, List *restrictinfo_list); static bool pred_test_recurse_clause(Expr *predicate, Node *clause); static bool pred_test_recurse_pred(Expr *predicate, Node *clause); static bool pred_test_simple_clause(Expr *predicate, Node *clause); -static void indexable_joinclauses(RelOptInfo *rel, IndexOptInfo *index, - List *joininfo_list, List *restrictinfo_list, - List **clausegroups, List **outerrelids); -static List *index_innerjoin(Query *root, RelOptInfo *rel, IndexOptInfo *index, - List *clausegroup_list, List *outerrelids_list); +static Relids indexable_outerrelids(RelOptInfo *rel, IndexOptInfo *index); +static Path *make_innerjoin_index_path(Query *root, + RelOptInfo *rel, IndexOptInfo *index, + List *clausegroup); static bool match_index_to_operand(int indexkey, Var *operand, RelOptInfo *rel, IndexOptInfo *index); static bool function_index_operand(Expr *funcOpnd, RelOptInfo *rel, @@ -108,7 +101,6 @@ static Const *string_to_const(const char *str, Oid datatype); * create_index_paths() * Generate all interesting index paths for the given relation. * Candidate paths are added to the rel's pathlist (using add_path). - * Additional IndexPath nodes may also be added to rel's innerjoin list. * * To be considered for an index scan, an index must match one or more * restriction clauses or join clauses from the query's qual condition, @@ -123,12 +115,14 @@ static Const *string_to_const(const char *str, Oid datatype); * in its join clauses. In that context, values for the other rels' * attributes are available and fixed during any one scan of the indexpath. * - * An IndexPath is generated and submitted to add_path() for each index - * this routine deems potentially interesting for the current query. - * An innerjoin path is also generated for each interesting combination of - * outer join relations. The innerjoin paths are *not* passed to add_path(), - * but are appended to the "innerjoin" list of the relation for later - * consideration in nested-loop joins. + * An IndexPath is generated and submitted to add_path() for each plain index + * scan this routine deems potentially interesting for the current query. + * + * We also determine the set of other relids that participate in join + * clauses that could be used with each index. The actually best innerjoin + * path will be generated for each outer relation later on, but knowing the + * set of potential otherrels allows us to identify equivalent outer relations + * and avoid repeated computation. * * 'rel' is the relation for which we want to generate index paths */ @@ -137,6 +131,7 @@ create_index_paths(Query *root, RelOptInfo *rel) { List *restrictinfo_list = rel->baserestrictinfo; List *joininfo_list = rel->joininfo; + Relids all_join_outerrelids = NIL; List *ilist; foreach(ilist, rel->indexlist) @@ -146,8 +141,7 @@ create_index_paths(Query *root, RelOptInfo *rel) List *index_pathkeys; List *useful_pathkeys; bool index_is_ordered; - List *joinclausegroups; - List *joinouterrelids; + Relids join_outerrelids; /* * If this is a partial index, we can only use it if it passes the @@ -178,11 +172,7 @@ create_index_paths(Query *root, RelOptInfo *rel) /* * 2. Match the index against non-'or' restriction clauses. */ - restrictclauses = group_clauses_by_indexkey(rel, - index, - index->indexkeys, - index->classlist, - restrictinfo_list); + restrictclauses = group_clauses_by_indexkey(rel, index); /* * 3. Compute pathkeys describing index's ordering, if any, then @@ -234,26 +224,19 @@ create_index_paths(Query *root, RelOptInfo *rel) } /* - * 6. Create an innerjoin index path for each combination of other - * rels used in available join clauses. These paths will be - * considered as the inner side of nestloop joins against those - * sets of other rels. indexable_joinclauses() finds sets of - * clauses that can be used with each combination of outer rels, - * and index_innerjoin builds the paths themselves. We add the - * paths to the rel's innerjoin list, NOT to the result list. + * 6. Examine join clauses to see which ones are potentially + * usable with this index, and generate a list of all other relids + * that participate in such join clauses. We'll use this list later + * to recognize outer rels that are equivalent for joining purposes. + * We compute both per-index and overall-for-relation lists. */ - indexable_joinclauses(rel, index, - joininfo_list, restrictinfo_list, - &joinclausegroups, - &joinouterrelids); - if (joinclausegroups != NIL) - { - rel->innerjoin = nconc(rel->innerjoin, - index_innerjoin(root, rel, index, - joinclausegroups, - joinouterrelids)); - } + join_outerrelids = indexable_outerrelids(rel, index); + index->outer_relids = join_outerrelids; + all_join_outerrelids = set_unioni(all_join_outerrelids, + join_outerrelids); } + + rel->index_outer_relids = all_join_outerrelids; } @@ -397,14 +380,14 @@ match_or_subclause_to_indexkey(RelOptInfo *rel, foreach(item, clause->args) { if (match_clause_to_indexkey(rel, index, indexkey, opclass, - lfirst(item), false)) + lfirst(item))) return true; } return false; } else return match_clause_to_indexkey(rel, index, indexkey, opclass, - clause, false); + clause); } /*---------- @@ -466,13 +449,13 @@ extract_or_indexqual_conditions(RelOptInfo *rel, if (match_clause_to_indexkey(rel, index, curIndxKey, curClass, - subsubclause, false)) + subsubclause)) clausegroup = lappend(clausegroup, subsubclause); } } else if (match_clause_to_indexkey(rel, index, curIndxKey, curClass, - orsubclause, false)) + orsubclause)) clausegroup = makeList1(orsubclause); /* @@ -487,7 +470,7 @@ extract_or_indexqual_conditions(RelOptInfo *rel, if (match_clause_to_indexkey(rel, index, curIndxKey, curClass, - rinfo->clause, false)) + rinfo->clause)) clausegroup = lappend(clausegroup, rinfo->clause); } } @@ -503,7 +486,8 @@ extract_or_indexqual_conditions(RelOptInfo *rel, indexkeys++; classes++; - } while (!DoneMatchingIndexKeys(indexkeys, index)); + + } while (!DoneMatchingIndexKeys(indexkeys, classes)); if (quals == NIL) elog(ERROR, "extract_or_indexqual_conditions: no matching clause"); @@ -523,15 +507,12 @@ extract_or_indexqual_conditions(RelOptInfo *rel, * * 'rel' is the node of the relation itself. * 'index' is a index on 'rel'. - * 'indexkeys' are the index keys to be matched. - * 'classes' are the classes of the index operators on those keys. - * 'restrictinfo_list' is the list of available restriction clauses for 'rel'. * * Returns a list of all the RestrictInfo nodes for clauses that can be * used with this index. * * The list is ordered by index key. (This is not depended on by any part - * of the planner, as far as I can tell; but some parts of the executor + * of the planner, so far as I can tell; but some parts of the executor * do assume that the indxqual list ultimately delivered to the executor * is so ordered. One such place is _bt_orderkeys() in the btree support. * Perhaps that ought to be fixed someday --- tgl 7/00) @@ -544,15 +525,14 @@ extract_or_indexqual_conditions(RelOptInfo *rel, * clauses matching column C, because the executor couldn't use them anyway. */ static List * -group_clauses_by_indexkey(RelOptInfo *rel, - IndexOptInfo *index, - int *indexkeys, - Oid *classes, - List *restrictinfo_list) +group_clauses_by_indexkey(RelOptInfo *rel, IndexOptInfo *index) { List *clausegroup_list = NIL; + List *restrictinfo_list = rel->baserestrictinfo; + int *indexkeys = index->indexkeys; + Oid *classes = index->classlist; - if (restrictinfo_list == NIL || indexkeys[0] == 0) + if (restrictinfo_list == NIL) return NIL; do @@ -560,18 +540,17 @@ group_clauses_by_indexkey(RelOptInfo *rel, int curIndxKey = indexkeys[0]; Oid curClass = classes[0]; List *clausegroup = NIL; - List *curCinfo; + List *i; - foreach(curCinfo, restrictinfo_list) + foreach(i, restrictinfo_list) { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(curCinfo); + RestrictInfo *rinfo = (RestrictInfo *) lfirst(i); if (match_clause_to_indexkey(rel, index, curIndxKey, curClass, - rinfo->clause, - false)) + rinfo->clause)) clausegroup = lappend(clausegroup, rinfo); } @@ -587,71 +566,84 @@ group_clauses_by_indexkey(RelOptInfo *rel, indexkeys++; classes++; - } while (!DoneMatchingIndexKeys(indexkeys, index)); + } while (!DoneMatchingIndexKeys(indexkeys, classes)); /* clausegroup_list holds all matched clauses ordered by indexkeys */ return clausegroup_list; } /* - * group_clauses_by_ikey_for_joins - * Generates a list of join clauses that can be used with an index + * group_clauses_by_indexkey_for_join + * Generates a list of clauses that can be used with an index * to scan the inner side of a nestloop join. * * This is much like group_clauses_by_indexkey(), but we consider both - * join and restriction clauses. For each indexkey in the index, we - * accept both join and restriction clauses that match it, since both - * will make useful indexquals if the index is being used to scan the - * inner side of a nestloop join. But there must be at least one matching - * join clause, or we return NIL indicating that this index isn't useful - * for nestloop joining. + * join and restriction clauses. Any joinclause that uses only otherrels + * in the specified outer_relids is fair game. But there must be at least + * one such joinclause in the final list, otherwise we return NIL indicating + * that this index isn't interesting as an inner indexscan. (A scan using + * only restriction clauses shouldn't be created here, because a regular Path + * will already have been generated for it.) */ static List * -group_clauses_by_ikey_for_joins(RelOptInfo *rel, - IndexOptInfo *index, - int *indexkeys, - Oid *classes, - List *join_cinfo_list, - List *restr_cinfo_list) +group_clauses_by_indexkey_for_join(RelOptInfo *rel, IndexOptInfo *index, + Relids outer_relids, bool isouterjoin) { List *clausegroup_list = NIL; bool jfound = false; - - if (join_cinfo_list == NIL || indexkeys[0] == 0) - return NIL; + int *indexkeys = index->indexkeys; + Oid *classes = index->classlist; do { int curIndxKey = indexkeys[0]; Oid curClass = classes[0]; List *clausegroup = NIL; - List *curCinfo; + List *i; - foreach(curCinfo, join_cinfo_list) + /* Look for joinclauses that are usable with given outer_relids */ + foreach(i, rel->joininfo) { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(curCinfo); + JoinInfo *joininfo = (JoinInfo *) lfirst(i); + List *j; - if (match_clause_to_indexkey(rel, - index, - curIndxKey, - curClass, - rinfo->clause, - true)) + if (!is_subseti(joininfo->unjoined_relids, outer_relids)) + continue; + + foreach(j, joininfo->jinfo_restrictinfo) { - clausegroup = lappend(clausegroup, rinfo); - jfound = true; + RestrictInfo *rinfo = (RestrictInfo *) lfirst(j); + + /* Can't use pushed-down clauses in outer join */ + if (isouterjoin && rinfo->ispusheddown) + continue; + + if (match_join_clause_to_indexkey(rel, + index, + curIndxKey, + curClass, + rinfo->clause)) + { + clausegroup = lappend(clausegroup, rinfo); + jfound = true; + } } } - foreach(curCinfo, restr_cinfo_list) + + /* We can also use plain restriction clauses for the rel */ + foreach(i, rel->baserestrictinfo) { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(curCinfo); + RestrictInfo *rinfo = (RestrictInfo *) lfirst(i); + + /* Can't use pushed-down clauses in outer join */ + if (isouterjoin && rinfo->ispusheddown) + continue; if (match_clause_to_indexkey(rel, index, curIndxKey, curClass, - rinfo->clause, - false)) + rinfo->clause)) clausegroup = lappend(clausegroup, rinfo); } @@ -667,11 +659,10 @@ group_clauses_by_ikey_for_joins(RelOptInfo *rel, indexkeys++; classes++; - } while (!DoneMatchingIndexKeys(indexkeys, index)); + } while (!DoneMatchingIndexKeys(indexkeys, classes)); /* - * if no join clause was matched then there ain't clauses for joins at - * all. + * if no join clause was matched then forget it, per comments above. */ if (!jfound) { @@ -686,16 +677,12 @@ group_clauses_by_ikey_for_joins(RelOptInfo *rel, /* * match_clause_to_indexkey() - * Determines whether a restriction or join clause matches - * a key of an index. + * Determines whether a restriction clause matches a key of an index. * * To match, the clause: * - * (1a) for a restriction clause: must be in the form (indexkey op const) - * or (const op indexkey), or - * (1b) for a join clause: must be in the form (indexkey op others) - * or (others op indexkey), where others is an expression involving - * only vars of the other relation(s); and + * (1) must be in the form (indexkey op const) or (const op indexkey); + * and * (2) must contain an operator which is in the same class as the index * operator for this key, or is a "special" operator as recognized * by match_special_index_operator(). @@ -706,18 +693,11 @@ group_clauses_by_ikey_for_joins(RelOptInfo *rel, * We do not actually do the commuting here, but we check whether a * suitable commutator operator is available. * - * Note that in the join case, we already know that the clause as a - * whole uses vars from the interesting set of relations. But we need - * to defend against expressions like (a.f1 OP (b.f2 OP a.f3)); that's - * not processable by an indexscan nestloop join, whereas - * (a.f1 OP (b.f2 OP c.f3)) is. - * * 'rel' is the relation of interest. * 'index' is an index on 'rel'. * 'indexkey' is a key of 'index'. * 'opclass' is the corresponding operator class. * 'clause' is the clause to be tested. - * 'join' is true if we are considering this clause for joins. * * Returns true if the clause can be used with this index key. * @@ -729,8 +709,7 @@ match_clause_to_indexkey(RelOptInfo *rel, IndexOptInfo *index, int indexkey, Oid opclass, - Expr *clause, - bool join) + Expr *clause) { Var *leftop, *rightop; @@ -743,75 +722,124 @@ match_clause_to_indexkey(RelOptInfo *rel, if (!leftop || !rightop) return false; - if (!join) + /* + * Check for clauses of the form: + * (indexkey operator constant) or (constant operator indexkey). + * Anything that is a "pseudo constant" expression will do. + */ + if (match_index_to_operand(indexkey, leftop, rel, index) && + is_pseudo_constant_clause((Node *) rightop)) { + if (is_indexable_operator(clause, opclass, true)) + return true; + /* - * Not considering joins, so check for clauses of the form: - * (indexkey operator constant) or (constant operator indexkey). - * Anything that is a "pseudo constant" expression will do. + * If we didn't find a member of the index's opclass, see + * whether it is a "special" indexable operator. */ + if (match_special_index_operator(clause, opclass, true)) + return true; + return false; + } - if (match_index_to_operand(indexkey, leftop, rel, index) && - is_pseudo_constant_clause((Node *) rightop)) - { - if (is_indexable_operator(clause, opclass, true)) - return true; + if (match_index_to_operand(indexkey, rightop, rel, index) && + is_pseudo_constant_clause((Node *) leftop)) + { + if (is_indexable_operator(clause, opclass, false)) + return true; - /* - * If we didn't find a member of the index's opclass, see - * whether it is a "special" indexable operator. - */ - if (match_special_index_operator(clause, opclass, true)) - return true; - return false; - } - if (match_index_to_operand(indexkey, rightop, rel, index) && - is_pseudo_constant_clause((Node *) leftop)) - { - if (is_indexable_operator(clause, opclass, false)) - return true; + /* + * If we didn't find a member of the index's opclass, see + * whether it is a "special" indexable operator. + */ + if (match_special_index_operator(clause, opclass, false)) + return true; + return false; + } - /* - * If we didn't find a member of the index's opclass, see - * whether it is a "special" indexable operator. - */ - if (match_special_index_operator(clause, opclass, false)) - return true; - return false; - } + return false; +} + +/* + * match_join_clause_to_indexkey() + * Determines whether a join clause matches a key of an index. + * + * To match, the clause: + * + * (1) must be in the form (indexkey op others) or (others op indexkey), + * where others is an expression involving only vars of the other + * relation(s); and + * (2) must contain an operator which is in the same class as the index + * operator for this key, or is a "special" operator as recognized + * by match_special_index_operator(). + * + * As above, we must be able to commute the clause to put the indexkey + * on the left. + * + * Note that we already know that the clause as a whole uses vars from + * the interesting set of relations. But we need to defend against + * expressions like (a.f1 OP (b.f2 OP a.f3)); that's not processable by + * an indexscan nestloop join, whereas (a.f1 OP (b.f2 OP c.f3)) is. + * + * 'rel' is the relation of interest. + * 'index' is an index on 'rel'. + * 'indexkey' is a key of 'index'. + * 'opclass' is the corresponding operator class. + * 'clause' is the clause to be tested. + * + * Returns true if the clause can be used with this index key. + * + * NOTE: returns false if clause is an OR or AND clause; it is the + * responsibility of higher-level routines to cope with those. + */ +static bool +match_join_clause_to_indexkey(RelOptInfo *rel, + IndexOptInfo *index, + int indexkey, + Oid opclass, + Expr *clause) +{ + Var *leftop, + *rightop; + + /* Clause must be a binary opclause. */ + if (!is_opclause((Node *) clause)) + return false; + leftop = get_leftop(clause); + rightop = get_rightop(clause); + if (!leftop || !rightop) + return false; + + /* + * Check for an indexqual that could be handled by a nestloop + * join. We need the index key to be compared against an + * expression that uses none of the indexed relation's vars and + * contains no volatile functions. + */ + if (match_index_to_operand(indexkey, leftop, rel, index)) + { + List *othervarnos = pull_varnos((Node *) rightop); + bool isIndexable; + + isIndexable = + !intMember(lfirsti(rel->relids), othervarnos) && + !contain_volatile_functions((Node *) rightop) && + is_indexable_operator(clause, opclass, true); + freeList(othervarnos); + return isIndexable; } - else + + if (match_index_to_operand(indexkey, rightop, rel, index)) { - /* - * Check for an indexqual that could be handled by a nestloop - * join. We need the index key to be compared against an - * expression that uses none of the indexed relation's vars and - * contains no volatile functions. - */ - if (match_index_to_operand(indexkey, leftop, rel, index)) - { - List *othervarnos = pull_varnos((Node *) rightop); - bool isIndexable; - - isIndexable = - !intMember(lfirsti(rel->relids), othervarnos) && - !contain_volatile_functions((Node *) rightop) && - is_indexable_operator(clause, opclass, true); - freeList(othervarnos); - return isIndexable; - } - else if (match_index_to_operand(indexkey, rightop, rel, index)) - { - List *othervarnos = pull_varnos((Node *) leftop); - bool isIndexable; - - isIndexable = - !intMember(lfirsti(rel->relids), othervarnos) && - !contain_volatile_functions((Node *) leftop) && - is_indexable_operator(clause, opclass, false); - freeList(othervarnos); - return isIndexable; - } + List *othervarnos = pull_varnos((Node *) leftop); + bool isIndexable; + + isIndexable = + !intMember(lfirsti(rel->relids), othervarnos) && + !contain_volatile_functions((Node *) leftop) && + is_indexable_operator(clause, opclass, false); + freeList(othervarnos); + return isIndexable; } return false; @@ -1267,164 +1295,288 @@ pred_test_simple_clause(Expr *predicate, Node *clause) ****************************************************************************/ /* - * indexable_joinclauses - * Finds all groups of join clauses from among 'joininfo_list' that can - * be used in conjunction with 'index' for the inner scan of a nestjoin. - * - * Each clause group comes from a single joininfo node plus the current - * rel's restrictinfo list. Therefore, every clause in the group references - * the current rel plus the same set of other rels (except for the restrict - * clauses, which only reference the current rel). Therefore, this set - * of clauses could be used as an indexqual if the relation is scanned - * as the inner side of a nestloop join when the outer side contains - * (at least) all those "other rels". - * - * XXX Actually, given that we are considering a join that requires an - * outer rel set (A,B,C), we should use all qual clauses that reference - * any subset of these rels, not just the full set or none. This is - * doable with a doubly nested loop over joininfo_list; is it worth it? - * - * Returns two parallel lists of the same length: the clause groups, - * and the required outer rel set for each one. + * indexable_outerrelids + * Finds all other relids that participate in any indexable join clause + * for the specified index. Returns a list of relids. * * 'rel' is the relation for which 'index' is defined - * 'joininfo_list' is the list of JoinInfo nodes for 'rel' - * 'restrictinfo_list' is the list of restriction clauses for 'rel' - * '*clausegroups' receives a list of clause sublists - * '*outerrelids' receives a list of relid lists */ -static void -indexable_joinclauses(RelOptInfo *rel, IndexOptInfo *index, - List *joininfo_list, List *restrictinfo_list, - List **clausegroups, List **outerrelids) +static Relids +indexable_outerrelids(RelOptInfo *rel, IndexOptInfo *index) { - List *cg_list = NIL; - List *relid_list = NIL; + Relids outer_relids = NIL; List *i; - foreach(i, joininfo_list) + foreach(i, rel->joininfo) { JoinInfo *joininfo = (JoinInfo *) lfirst(i); - List *clausegroup; + bool match_found = false; + List *j; + + /* + * Examine each joinclause in the JoinInfo node's list to see if + * it matches any key of the index. If so, add the JoinInfo's + * otherrels to the result. We can skip examining other joinclauses + * in the same list as soon as we find a match (since by definition + * they all have the same otherrels). + */ + foreach(j, joininfo->jinfo_restrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(j); + Expr *clause = rinfo->clause; + int *indexkeys = index->indexkeys; + Oid *classes = index->classlist; + + do + { + int curIndxKey = indexkeys[0]; + Oid curClass = classes[0]; + + if (match_join_clause_to_indexkey(rel, + index, + curIndxKey, + curClass, + clause)) + { + match_found = true; + break; + } + + indexkeys++; + classes++; - clausegroup = group_clauses_by_ikey_for_joins(rel, - index, - index->indexkeys, - index->classlist, - joininfo->jinfo_restrictinfo, - restrictinfo_list); + } while (!DoneMatchingIndexKeys(indexkeys, classes)); - if (clausegroup != NIL) + if (match_found) + break; + } + + if (match_found) { - cg_list = lappend(cg_list, clausegroup); - relid_list = lappend(relid_list, joininfo->unjoined_relids); + outer_relids = set_unioni(outer_relids, + joininfo->unjoined_relids); } } - *clausegroups = cg_list; - *outerrelids = relid_list; + return outer_relids; } -/**************************************************************************** - * ---- PATH CREATION UTILITIES ---- - ****************************************************************************/ - /* - * index_innerjoin - * Creates index path nodes corresponding to paths to be used as inner - * relations in nestloop joins. - * - * 'rel' is the relation for which 'index' is defined - * 'clausegroup_list' is a list of lists of restrictinfo nodes which can use - * 'index'. Each sublist refers to the same set of outer rels. - * 'outerrelids_list' is a list of the required outer rels for each sublist - * of join clauses. + * best_inner_indexscan + * Finds the best available inner indexscan for a nestloop join + * with the given rel on the inside and the given outer_relids outside. + * May return NULL if there are no possible inner indexscans. * - * Returns a list of index pathnodes. + * We ignore ordering considerations (since a nestloop's inner scan's order + * is uninteresting). Also, we consider only total cost when deciding which + * of two possible paths is better --- this assumes that all indexpaths have + * negligible startup cost. (True today, but someday we might have to think + * harder.) Therefore, there is only one dimension of comparison and so it's + * sufficient to return a single "best" path. */ -static List * -index_innerjoin(Query *root, RelOptInfo *rel, IndexOptInfo *index, - List *clausegroup_list, List *outerrelids_list) +Path * +best_inner_indexscan(Query *root, RelOptInfo *rel, + Relids outer_relids, JoinType jointype) { - List *path_list = NIL; - List *i; + Path *cheapest = NULL; + bool isouterjoin; + List *ilist; + List *jlist; + InnerIndexscanInfo *info; - foreach(i, clausegroup_list) + /* + * Nestloop only supports inner and left joins. + */ + switch (jointype) { - List *clausegroup = lfirst(i); - IndexPath *pathnode = makeNode(IndexPath); - List *indexquals = NIL; - bool alljoinquals = true; - List *temp; - - /* XXX this code ought to be merged with create_index_path? */ - - pathnode->path.pathtype = T_IndexScan; - pathnode->path.parent = rel; + case JOIN_INNER: + isouterjoin = false; + break; + case JOIN_LEFT: + isouterjoin = true; + break; + default: + return NULL; + } + /* + * If there are no indexable joinclauses for this rel, exit quickly. + * Otherwise, intersect the given outer_relids with index_outer_relids + * to find the set of outer relids actually relevant for this index. + * If there are none, again we can fail immediately. + */ + if (!rel->index_outer_relids) + return NULL; + outer_relids = set_intersecti(rel->index_outer_relids, outer_relids); + if (!outer_relids) + return NULL; + /* + * Look to see if we already computed the result for this set of + * relevant outerrels. (We include the isouterjoin status in the + * cache lookup key for safety. In practice I suspect this is not + * necessary because it should always be the same for a given innerrel.) + */ + foreach(jlist, rel->index_inner_paths) + { + info = (InnerIndexscanInfo *) lfirst(jlist); + if (sameseti(info->other_relids, outer_relids) && + info->isouterjoin == isouterjoin) + { + freeList(outer_relids); + return info->best_innerpath; + } + } + /* + * For each index of the rel, find the best path; then choose the + * best overall. We cache the per-index results as well as the overall + * result. (This is useful because different indexes may have different + * relevant outerrel sets, so different overall outerrel sets might still + * map to the same computation for a given index.) + */ + foreach(ilist, rel->indexlist) + { + IndexOptInfo *index = (IndexOptInfo *) lfirst(ilist); + Relids index_outer_relids; + Path *path = NULL; + + /* skip quickly if index has no useful join clauses */ + if (!index->outer_relids) + continue; + /* identify set of relevant outer relids for this index */ + index_outer_relids = set_intersecti(index->outer_relids, outer_relids); + if (!index_outer_relids) + continue; /* - * There's no point in marking the path with any pathkeys, since - * it will only ever be used as the inner path of a nestloop, and - * so its ordering does not matter. + * Look to see if we already computed the result for this index. */ - pathnode->path.pathkeys = NIL; + foreach(jlist, index->inner_paths) + { + info = (InnerIndexscanInfo *) lfirst(jlist); + if (sameseti(info->other_relids, index_outer_relids) && + info->isouterjoin == isouterjoin) + { + path = info->best_innerpath; + freeList(index_outer_relids); /* not needed anymore */ + break; + } + } - /* extract bare indexqual clauses, check whether all from JOIN/ON */ - foreach(temp, clausegroup) + if (jlist == NIL) /* failed to find a match? */ { - RestrictInfo *clause = (RestrictInfo *) lfirst(temp); + List *clausegroup; + + /* find useful clauses for this index and outerjoin set */ + clausegroup = group_clauses_by_indexkey_for_join(rel, + index, + index_outer_relids, + isouterjoin); + if (clausegroup) + { + /* remove duplicate and redundant clauses */ + clausegroup = remove_redundant_join_clauses(root, + clausegroup, + jointype); + /* make the path */ + path = make_innerjoin_index_path(root, rel, index, + clausegroup); + } - indexquals = lappend(indexquals, clause->clause); - if (clause->ispusheddown) - alljoinquals = false; + /* Cache the result --- whether positive or negative */ + info = makeNode(InnerIndexscanInfo); + info->other_relids = index_outer_relids; + info->isouterjoin = isouterjoin; + info->best_innerpath = path; + index->inner_paths = lcons(info, index->inner_paths); } - /* expand special operators to indexquals the executor can handle */ - indexquals = expand_indexqual_conditions(indexquals); + if (path != NULL && + (cheapest == NULL || + compare_path_costs(path, cheapest, TOTAL_COST) < 0)) + cheapest = path; + } - /* - * Note that we are making a pathnode for a single-scan indexscan; - * therefore, both indexinfo and indexqual should be - * single-element lists. - */ - pathnode->indexinfo = makeList1(index); - pathnode->indexqual = makeList1(indexquals); + /* Cache the result --- whether positive or negative */ + info = makeNode(InnerIndexscanInfo); + info->other_relids = outer_relids; + info->isouterjoin = isouterjoin; + info->best_innerpath = cheapest; + rel->index_inner_paths = lcons(info, rel->index_inner_paths); - /* We don't actually care what order the index scans in ... */ - pathnode->indexscandir = NoMovementScanDirection; + return cheapest; +} - /* joinrelids saves the rels needed on the outer side of the join */ - pathnode->joinrelids = lfirst(outerrelids_list); +/**************************************************************************** + * ---- PATH CREATION UTILITIES ---- + ****************************************************************************/ - pathnode->alljoinquals = alljoinquals; +/* + * make_innerjoin_index_path + * Create an index path node for a path to be used as an inner + * relation in a nestloop join. + * + * 'rel' is the relation for which 'index' is defined + * 'clausegroup' is a list of restrictinfo nodes that can use 'index' + */ +static Path * +make_innerjoin_index_path(Query *root, + RelOptInfo *rel, IndexOptInfo *index, + List *clausegroup) +{ + IndexPath *pathnode = makeNode(IndexPath); + List *indexquals; - /* - * We must compute the estimated number of output rows for the - * indexscan. This is less than rel->rows because of the - * additional selectivity of the join clauses. Since clausegroup - * may contain both restriction and join clauses, we have to do a - * set union to get the full set of clauses that must be - * considered to compute the correct selectivity. (We can't just - * nconc the two lists; then we might have some restriction - * clauses appearing twice, which'd mislead - * restrictlist_selectivity into double-counting their - * selectivity.) - */ - pathnode->rows = rel->tuples * - restrictlist_selectivity(root, - set_union(rel->baserestrictinfo, - clausegroup), - lfirsti(rel->relids)); - /* Like costsize.c, force estimate to be at least one row */ - if (pathnode->rows < 1.0) - pathnode->rows = 1.0; - - cost_index(&pathnode->path, root, rel, index, indexquals, true); - - path_list = lappend(path_list, pathnode); - outerrelids_list = lnext(outerrelids_list); - } - return path_list; + /* XXX this code ought to be merged with create_index_path? */ + + pathnode->path.pathtype = T_IndexScan; + pathnode->path.parent = rel; + + /* + * There's no point in marking the path with any pathkeys, since + * it will only ever be used as the inner path of a nestloop, and + * so its ordering does not matter. + */ + pathnode->path.pathkeys = NIL; + + /* Extract bare indexqual clauses from restrictinfos */ + indexquals = get_actual_clauses(clausegroup); + + /* expand special operators to indexquals the executor can handle */ + indexquals = expand_indexqual_conditions(indexquals); + + /* + * Note that we are making a pathnode for a single-scan indexscan; + * therefore, both indexinfo and indexqual should be single-element lists. + */ + pathnode->indexinfo = makeList1(index); + pathnode->indexqual = makeList1(indexquals); + + /* We don't actually care what order the index scans in ... */ + pathnode->indexscandir = NoMovementScanDirection; + + /* + * We must compute the estimated number of output rows for the + * indexscan. This is less than rel->rows because of the + * additional selectivity of the join clauses. Since clausegroup + * may contain both restriction and join clauses, we have to do a + * set union to get the full set of clauses that must be + * considered to compute the correct selectivity. (We can't just + * nconc the two lists; then we might have some restriction + * clauses appearing twice, which'd mislead + * restrictlist_selectivity into double-counting their + * selectivity.) + */ + pathnode->rows = rel->tuples * + restrictlist_selectivity(root, + set_union(rel->baserestrictinfo, + clausegroup), + lfirsti(rel->relids)); + /* Like costsize.c, force estimate to be at least one row */ + if (pathnode->rows < 1.0) + pathnode->rows = 1.0; + + cost_index(&pathnode->path, root, rel, index, indexquals, true); + + return (Path *) pathnode; } /**************************************************************************** diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index 8e73bd2f419..ac5d4a72d45 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinpath.c,v 1.71 2002/09/04 20:31:20 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinpath.c,v 1.72 2002/11/24 21:52:14 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -42,8 +42,6 @@ static void match_unsorted_inner(Query *root, RelOptInfo *joinrel, static void hash_inner_and_outer(Query *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel, List *restrictlist, JoinType jointype); -static Path *best_innerjoin(List *join_paths, List *outer_relid, - JoinType jointype); static List *select_mergejoin_clauses(RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel, @@ -351,8 +349,8 @@ match_unsorted_outer(Query *root, * Get the best innerjoin indexpath (if any) for this outer rel. It's * the same for all outer paths. */ - bestinnerjoin = best_innerjoin(innerrel->innerjoin, outerrel->relids, - jointype); + bestinnerjoin = best_inner_indexscan(root, innerrel, + outerrel->relids, jointype); foreach(i, outerrel->pathlist) { @@ -813,69 +811,6 @@ hash_inner_and_outer(Query *root, } /* - * best_innerjoin - * Find the cheapest index path that has already been identified by - * indexable_joinclauses() as being a possible inner path for the given - * outer relation(s) in a nestloop join. - * - * We compare indexpaths on total_cost only, assuming that they will all have - * zero or negligible startup_cost. We might have to think harder someday... - * - * 'join_paths' is a list of potential inner indexscan join paths - * 'outer_relids' is the relid list of the outer join relation - * - * Returns the pathnode of the best path, or NULL if there's no - * usable path. - */ -static Path * -best_innerjoin(List *join_paths, Relids outer_relids, JoinType jointype) -{ - Path *cheapest = (Path *) NULL; - bool isouterjoin; - List *join_path; - - /* - * Nestloop only supports inner and left joins. - */ - switch (jointype) - { - case JOIN_INNER: - isouterjoin = false; - break; - case JOIN_LEFT: - isouterjoin = true; - break; - default: - return NULL; - } - - foreach(join_path, join_paths) - { - IndexPath *path = (IndexPath *) lfirst(join_path); - - Assert(IsA(path, IndexPath)); - - /* - * If processing an outer join, only use explicit join clauses in - * the inner indexscan. For inner joins we need not be so picky. - */ - if (isouterjoin && !path->alljoinquals) - continue; - - /* - * path->joinrelids is the set of base rels that must be part of - * outer_relids in order to use this inner path, because those - * rels are used in the index join quals of this inner path. - */ - if (is_subseti(path->joinrelids, outer_relids) && - (cheapest == NULL || - compare_path_costs((Path *) path, cheapest, TOTAL_COST) < 0)) - cheapest = (Path *) path; - } - return cheapest; -} - -/* * select_mergejoin_clauses * Select mergejoin clauses that are usable for a particular join. * Returns a list of RestrictInfo nodes for those clauses. diff --git a/src/backend/optimizer/path/orindxpath.c b/src/backend/optimizer/path/orindxpath.c index f0c1a44196d..009afdff079 100644 --- a/src/backend/optimizer/path/orindxpath.c +++ b/src/backend/optimizer/path/orindxpath.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/path/orindxpath.c,v 1.47 2002/06/20 20:29:30 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/orindxpath.c,v 1.48 2002/11/24 21:52:14 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -92,9 +92,6 @@ create_or_index_paths(Query *root, RelOptInfo *rel) /* We don't actually care what order the index scans in. */ pathnode->indexscandir = NoMovementScanDirection; - /* This isn't a nestloop innerjoin, so: */ - pathnode->joinrelids = NIL; /* no join clauses here */ - pathnode->alljoinquals = false; pathnode->rows = rel->rows; best_or_subclause_indices(root, diff --git a/src/backend/optimizer/path/tidpath.c b/src/backend/optimizer/path/tidpath.c index f8d4f79d4db..27fe9e281f3 100644 --- a/src/backend/optimizer/path/tidpath.c +++ b/src/backend/optimizer/path/tidpath.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/path/tidpath.c,v 1.11 2002/09/05 00:43:06 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/tidpath.c,v 1.12 2002/11/24 21:52:14 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -25,7 +25,6 @@ #include "parser/parse_coerce.h" #include "utils/lsyscache.h" -static void create_tidscan_joinpaths(Query *root, RelOptInfo *rel); static List *TidqualFromRestrictinfo(List *relids, List *restrictinfo); static bool isEvaluable(int varno, Node *node); static Node *TidequalClause(int varno, Expr *node); @@ -237,44 +236,6 @@ TidqualFromRestrictinfo(List *relids, List *restrictinfo) } /* - * create_tidscan_joinpaths - * Create innerjoin paths if there are suitable joinclauses. - * - * XXX does this actually work? - */ -static void -create_tidscan_joinpaths(Query *root, RelOptInfo *rel) -{ - List *rlst = NIL, - *lst; - - foreach(lst, rel->joininfo) - { - JoinInfo *joininfo = (JoinInfo *) lfirst(lst); - List *restinfo, - *tideval; - - restinfo = joininfo->jinfo_restrictinfo; - tideval = TidqualFromRestrictinfo(rel->relids, restinfo); - if (length(tideval) == 1) - { - TidPath *pathnode = makeNode(TidPath); - - pathnode->path.pathtype = T_TidScan; - pathnode->path.parent = rel; - pathnode->path.pathkeys = NIL; - pathnode->tideval = tideval; - pathnode->unjoined_relids = joininfo->unjoined_relids; - - cost_tidscan(&pathnode->path, root, rel, tideval); - - rlst = lappend(rlst, pathnode); - } - } - rel->innerjoin = nconc(rel->innerjoin, rlst); -} - -/* * create_tidscan_paths * Creates paths corresponding to tid direct scans of the given rel. * Candidate paths are added to the rel's pathlist (using add_path). @@ -287,5 +248,4 @@ create_tidscan_paths(Query *root, RelOptInfo *rel) if (tideval) add_path(rel, (Path *) create_tidscan_path(root, rel, tideval)); - create_tidscan_joinpaths(root, rel); } diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index e43c52f6dfe..529ba712f41 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/initsplan.c,v 1.76 2002/11/19 23:21:58 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/initsplan.c,v 1.77 2002/11/24 21:52:14 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -577,15 +577,12 @@ distribute_qual_to_rels(Query *root, Node *clause, * the relid list. Set additional RestrictInfo fields for * joining. * - * We don't bother setting the merge/hashjoin info if we're not going - * to need it. We do want to know about mergejoinable ops in any - * potential equijoin clause (see later in this routine), and we - * ignore enable_mergejoin if isouterjoin is true, because - * mergejoin is the only implementation we have for full and right - * outer joins. + * 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. */ - if (enable_mergejoin || isouterjoin || can_be_equijoin) - check_mergejoinable(restrictinfo); + check_mergejoinable(restrictinfo); if (enable_hashjoin) check_hashjoinable(restrictinfo); diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 7dd0dce6891..e99435a6edf 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/util/pathnode.c,v 1.79 2002/11/06 00:00:44 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/pathnode.c,v 1.80 2002/11/24 21:52:14 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -354,12 +354,9 @@ create_index_path(Query *root, pathnode->indexscandir = indexscandir; /* - * This routine is only used to generate "standalone" indexpaths, not - * nestloop inner indexpaths. So joinrelids is always NIL and the - * number of rows is the same as the parent rel's estimate. + * The number of rows is the same as the parent rel's estimate, since + * this isn't a join inner indexscan. */ - pathnode->joinrelids = NIL; /* no join clauses here */ - pathnode->alljoinquals = false; pathnode->rows = rel->rows; /* diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 3aee668448d..15120fafcd8 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/util/plancat.c,v 1.74 2002/09/04 20:31:22 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/plancat.c,v 1.75 2002/11/24 21:52:14 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -172,6 +172,10 @@ find_secondary_indexes(Oid relationObjectId) } } + /* initialize cached join info to empty */ + info->outer_relids = NIL; + info->inner_paths = NIL; + index_close(indexRelation); indexinfos = lcons(info, indexinfos); diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index a07a208ecff..b93816bd21b 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/util/relnode.c,v 1.40 2002/10/12 22:24:49 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/relnode.c,v 1.41 2002/11/24 21:52:14 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -17,8 +17,8 @@ #include "optimizer/cost.h" #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" -#include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" #include "parser/parsetree.h" @@ -152,7 +152,8 @@ make_base_rel(Query *root, int relid) rel->baserestrictcost = 0; rel->outerjoinset = NIL; rel->joininfo = NIL; - rel->innerjoin = NIL; + rel->index_outer_relids = NIL; + rel->index_inner_paths = NIL; /* Check type of rtable entry */ switch (rte->rtekind) @@ -365,7 +366,8 @@ build_join_rel(Query *root, joinrel->baserestrictcost = 0; joinrel->outerjoinset = NIL; joinrel->joininfo = NIL; - joinrel->innerjoin = NIL; + joinrel->index_outer_relids = NIL; + joinrel->index_inner_paths = NIL; /* Is there a join RTE matching this join? */ joinrterel = find_other_rel_for_join(root, joinrelids); @@ -529,9 +531,8 @@ build_joinrel_restrictlist(Query *root, RelOptInfo *inner_rel, JoinType jointype) { - List *result = NIL; + List *result; List *rlist; - List *item; /* * Collect all the clauses that syntactically belong at this level. @@ -549,59 +550,8 @@ build_joinrel_restrictlist(Query *root, * mergejoinable clause, it's possible that it is redundant with * previous clauses (see optimizer/README for discussion). We detect * that case and omit the redundant clause from the result list. - * - * We can detect redundant mergejoinable clauses very cheaply by using - * their left and right pathkeys, which uniquely identify the sets of - * equijoined variables in question. All the members of a pathkey set - * that are in the left relation have already been forced to be equal; - * likewise for those in the right relation. So, we need to have only - * one clause that checks equality between any set member on the left - * and any member on the right; by transitivity, all the rest are then - * equal. - * - * Weird special case: if we have two clauses that seem redundant - * except one is pushed down into an outer join and the other isn't, - * then they're not really redundant, because one constrains the - * joined rows after addition of null fill rows, and the other doesn't. */ - foreach(item, rlist) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(item); - - /* eliminate duplicates */ - if (member(rinfo, result)) - continue; - - /* check for redundant merge clauses */ - if (rinfo->mergejoinoperator != InvalidOid) - { - bool redundant = false; - List *olditem; - - cache_mergeclause_pathkeys(root, rinfo); - - foreach(olditem, result) - { - RestrictInfo *oldrinfo = (RestrictInfo *) lfirst(olditem); - - if (oldrinfo->mergejoinoperator != InvalidOid && - rinfo->left_pathkey == oldrinfo->left_pathkey && - rinfo->right_pathkey == oldrinfo->right_pathkey && - (rinfo->ispusheddown == oldrinfo->ispusheddown || - !IS_OUTER_JOIN(jointype))) - { - redundant = true; - break; - } - } - - if (redundant) - continue; - } - - /* otherwise, add it to result list */ - result = lappend(result, rinfo); - } + result = remove_redundant_join_clauses(root, rlist, jointype); freeList(rlist); diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c index c9f1e75e232..bc1fcc36464 100644 --- a/src/backend/optimizer/util/restrictinfo.c +++ b/src/backend/optimizer/util/restrictinfo.c @@ -8,21 +8,21 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/util/restrictinfo.c,v 1.14 2002/06/20 20:29:31 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/restrictinfo.c,v 1.15 2002/11/24 21:52:14 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" - #include "optimizer/clauses.h" +#include "optimizer/paths.h" #include "optimizer/restrictinfo.h" + /* * restriction_is_or_clause * * Returns t iff the restrictinfo node contains an 'or' clause. - * */ bool restriction_is_or_clause(RestrictInfo *restrictinfo) @@ -37,8 +37,7 @@ restriction_is_or_clause(RestrictInfo *restrictinfo) /* * get_actual_clauses * - * Returns a list containing the clauses from 'restrictinfo_list'. - * + * Returns a list containing the bare clauses from 'restrictinfo_list'. */ List * get_actual_clauses(List *restrictinfo_list) @@ -80,3 +79,81 @@ get_actual_join_clauses(List *restrictinfo_list, *joinquals = lappend(*joinquals, clause->clause); } } + +/* + * remove_redundant_join_clauses + * + * Given a list of RestrictInfo clauses that are to be applied in a join, + * remove any duplicate or redundant clauses. + * + * We must eliminate duplicates when forming the restrictlist for a joinrel, + * since we will see many of the same clauses arriving from both input + * relations. Also, if a clause is a mergejoinable clause, it's possible that + * it is redundant with previous clauses (see optimizer/README for + * discussion). We detect that case and omit the redundant clause from the + * result list. + * + * We can detect redundant mergejoinable clauses very cheaply by using their + * left and right pathkeys, which uniquely identify the sets of equijoined + * variables in question. All the members of a pathkey set that are in the + * left relation have already been forced to be equal; likewise for those in + * the right relation. So, we need to have only one clause that checks + * equality between any set member on the left and any member on the right; + * by transitivity, all the rest are then equal. + * + * Weird special case: if we have two clauses that seem redundant + * except one is pushed down into an outer join and the other isn't, + * then they're not really redundant, because one constrains the + * joined rows after addition of null fill rows, and the other doesn't. + * + * The result is a fresh List, but it points to the same member nodes + * as were in the input. + */ +List * +remove_redundant_join_clauses(Query *root, List *restrictinfo_list, + JoinType jointype) +{ + List *result = NIL; + List *item; + + foreach(item, restrictinfo_list) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(item); + + /* eliminate duplicates */ + if (member(rinfo, result)) + continue; + + /* check for redundant merge clauses */ + if (rinfo->mergejoinoperator != InvalidOid) + { + bool redundant = false; + List *olditem; + + cache_mergeclause_pathkeys(root, rinfo); + + foreach(olditem, result) + { + RestrictInfo *oldrinfo = (RestrictInfo *) lfirst(olditem); + + if (oldrinfo->mergejoinoperator != InvalidOid && + rinfo->left_pathkey == oldrinfo->left_pathkey && + rinfo->right_pathkey == oldrinfo->right_pathkey && + (rinfo->ispusheddown == oldrinfo->ispusheddown || + !IS_OUTER_JOIN(jointype))) + { + redundant = true; + break; + } + } + + if (redundant) + continue; + } + + /* otherwise, add it to result list */ + result = lappend(result, rinfo); + } + + return result; +} diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 112eac34680..d2b984de4f8 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: nodes.h,v 1.123 2002/11/15 02:50:10 momjian Exp $ + * $Id: nodes.h,v 1.124 2002/11/24 21:52:14 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -87,6 +87,7 @@ typedef enum NodeTag T_RestrictInfo, T_JoinInfo, T_IndexOptInfo, + T_InnerIndexscanInfo, /* * TAGS FOR EXECUTOR NODES (execnodes.h) diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h index e1de57b53e9..9ef4fab957e 100644 --- a/src/include/nodes/pg_list.h +++ b/src/include/nodes/pg_list.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: pg_list.h,v 1.29 2002/08/19 00:10:03 tgl Exp $ + * $Id: pg_list.h,v 1.30 2002/11/24 21:52:15 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -141,6 +141,7 @@ extern List *set_differencei(List *list1, List *list2); extern List *lreverse(List *l); extern List *set_union(List *list1, List *list2); extern List *set_unioni(List *list1, List *list2); +extern List *set_intersecti(List *list1, List *list2); extern bool equali(List *list1, List *list2); extern bool sameseti(List *list1, List *list2); diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 480fd9630be..d441e6bdc49 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: relation.h,v 1.68 2002/11/06 00:00:44 tgl Exp $ + * $Id: relation.h,v 1.69 2002/11/24 21:52:15 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -129,9 +129,11 @@ typedef enum CostSelector * syntactically within the join. Otherwise, unused. * joininfo - List of JoinInfo nodes, containing info about each join * clause in which this relation participates - * innerjoin - List of Path nodes that represent indices that may be used - * as inner paths of nestloop joins. This field is non-null - * only for base rels, since join rels have no indices. + * index_outer_relids - only used for base rels; list of outer relids + * that participate in indexable joinclauses for this rel + * index_inner_paths - only used for base rels; list of InnerIndexscanInfo + * nodes showing best indexpaths for various subsets of + * index_outer_relids. * * Note: Keeping a restrictinfo list in the RelOptInfo is useful only for * base rels, because for a join rel the set of clauses that are treated as @@ -200,12 +202,17 @@ typedef struct RelOptInfo Cost baserestrictcost; /* cost of evaluating the above */ Relids outerjoinset; /* integer list of base relids */ List *joininfo; /* JoinInfo structures */ - List *innerjoin; /* potential indexscans for nestloop joins */ + /* cached info about inner indexscan paths for relation: */ + Relids index_outer_relids; /* other relids in indexable join + * clauses */ + List *index_inner_paths; /* InnerIndexscanInfo nodes */ /* - * innerjoin indexscans are not in the main pathlist because they are - * not usable except in specific join contexts; we have to test before - * seeing whether they can be used. + * Inner indexscans are not in the main pathlist because they are + * not usable except in specific join contexts. We use the + * index_inner_paths list just to avoid recomputing the best inner + * indexscan repeatedly for similar outer relations. See comments + * for InnerIndexscanInfo. */ } RelOptInfo; @@ -217,20 +224,6 @@ typedef struct RelOptInfo * and indexes, but that created confusion without actually doing anything * useful. So now we have a separate IndexOptInfo struct for indexes. * - * indexoid - OID of the index relation itself - * pages - number of disk pages in index - * tuples - number of index tuples in index - * ncolumns - number of columns in index - * nkeys - number of keys used by index (input columns) - * classlist - List of PG_OPCLASS OIDs for the index - * indexkeys - List of base-relation attribute numbers that are index keys - * ordering - List of PG_OPERATOR OIDs which order the indexscan result - * relam - the OID of the pg_am of the index - * amcostestimate - OID of the relam's cost estimator - * indproc - OID of the function if a functional index, else 0 - * indpred - index predicate if a partial index, else NULL - * unique - true if index is unique - * * ncolumns and nkeys are the same except for a functional index, * wherein ncolumns is 1 (the single function output) while nkeys * is the number of table columns passed to the function. classlist[] @@ -249,22 +242,26 @@ typedef struct IndexOptInfo Oid indexoid; /* OID of the index relation */ /* statistics from pg_class */ - long pages; - double tuples; + long pages; /* number of disk pages in index */ + double tuples; /* number of index tuples in index */ /* index descriptor information */ int ncolumns; /* number of columns in index */ int nkeys; /* number of keys used by index */ - Oid *classlist; /* AM operator classes for columns */ + Oid *classlist; /* OIDs of operator classes for columns */ int *indexkeys; /* column numbers of index's keys */ Oid *ordering; /* OIDs of sort operators for each column */ Oid relam; /* OID of the access method (in pg_am) */ RegProcedure amcostestimate; /* OID of the access method's cost fcn */ - Oid indproc; /* if a functional index */ - List *indpred; /* if a partial index */ - bool unique; /* if a unique index */ + Oid indproc; /* OID of func if functional index, else 0 */ + List *indpred; /* predicate if a partial index, else NIL */ + bool unique; /* true if a unique index */ + + /* cached info about inner indexscan paths for index */ + Relids outer_relids; /* other relids in usable join clauses */ + List *inner_paths; /* List of InnerIndexscanInfo nodes */ } IndexOptInfo; @@ -354,18 +351,9 @@ typedef struct Path * NoMovementScanDirection for an indexscan, but the planner wants to * distinguish ordered from unordered indexes for building pathkeys.) * - * 'joinrelids' is only used in IndexPaths that are constructed for use - * as the inner path of a nestloop join. These paths have indexquals - * that refer to values of other rels, so those other rels must be - * included in the outer joinrel in order to make a usable join. - * - * 'alljoinquals' is also used only for inner paths of nestloop joins. - * This flag is TRUE iff all the indexquals came from non-pushed-down - * JOIN/ON conditions, which means the path is safe to use for an outer join. - * * 'rows' is the estimated result tuple count for the indexscan. This * is the same as path.parent->rows for a simple indexscan, but it is - * different for a nestloop inner path, because the additional indexquals + * different for a nestloop inner scan, because the additional indexquals * coming from join clauses make the scan more selective than the parent * rel's restrict clauses alone would do. *---------- @@ -376,8 +364,6 @@ typedef struct IndexPath List *indexinfo; List *indexqual; ScanDirection indexscandir; - Relids joinrelids; /* other rels mentioned in indexqual */ - bool alljoinquals; /* all indexquals derived from JOIN conds? */ double rows; /* estimated number of result tuples */ } IndexPath; @@ -616,4 +602,42 @@ typedef struct JoinInfo List *jinfo_restrictinfo; /* relevant RestrictInfos */ } JoinInfo; +/* + * Inner indexscan info. + * + * An inner indexscan is one that uses one or more joinclauses as index + * conditions (perhaps in addition to plain restriction clauses). So it + * can only be used as the inner path of a nestloop join where the outer + * relation includes all other relids appearing in those joinclauses. + * The set of usable joinclauses, and thus the best inner indexscan, + * thus varies depending on which outer relation we consider; so we have + * to recompute the best such path for every join. To avoid lots of + * redundant computation, we cache the results of such searches. For + * each index we compute the set of possible otherrelids (all relids + * appearing in joinquals that could become indexquals for this index). + * Two outer relations whose relids have the same intersection with this + * set will have the same set of available joinclauses and thus the same + * best inner indexscan for that index. Similarly, for each base relation, + * we form the union of the per-index otherrelids sets. Two outer relations + * with the same intersection with that set will have the same best overall + * inner indexscan for the base relation. We use lists of InnerIndexscanInfo + * nodes to cache the results of these searches at both the index and + * relation level. + * + * The search key also includes a bool showing whether the join being + * considered is an outer join. Since we constrain the join order for + * outer joins, I believe that this bool can only have one possible value + * for any particular base relation; but store it anyway to avoid confusion. + */ + +typedef struct InnerIndexscanInfo +{ + NodeTag type; + /* The lookup key: */ + Relids other_relids; /* a set of relevant other relids */ + bool isouterjoin; /* true if join is outer */ + /* Best path for this lookup key: */ + Path *best_innerpath; /* best inner indexscan, or NULL if none */ +} InnerIndexscanInfo; + #endif /* RELATION_H */ diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 4222c77ad97..b03ce0453e6 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: paths.h,v 1.60 2002/06/20 20:29:51 momjian Exp $ + * $Id: paths.h,v 1.61 2002/11/24 21:52:15 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -40,6 +40,8 @@ extern void debug_print_rel(Query *root, RelOptInfo *rel); * routines to generate index paths */ extern void create_index_paths(Query *root, RelOptInfo *rel); +extern Path *best_inner_indexscan(Query *root, RelOptInfo *rel, + Relids outer_relids, JoinType jointype); extern Oid indexable_operator(Expr *clause, Oid opclass, bool indexkey_on_left); extern List *extract_or_indexqual_conditions(RelOptInfo *rel, diff --git a/src/include/optimizer/restrictinfo.h b/src/include/optimizer/restrictinfo.h index 3806415fe8c..997d4ab8c52 100644 --- a/src/include/optimizer/restrictinfo.h +++ b/src/include/optimizer/restrictinfo.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: restrictinfo.h,v 1.15 2002/06/20 20:29:51 momjian Exp $ + * $Id: restrictinfo.h,v 1.16 2002/11/24 21:52:15 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -20,5 +20,8 @@ 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, List **joinquals, List **otherquals); +extern List *remove_redundant_join_clauses(Query *root, + List *restrictinfo_list, + JoinType jointype); #endif /* RESTRICTINFO_H */ |