Commit 04c8785c authored by Tom Lane's avatar Tom Lane

Restructure planning of nestloop inner indexscans so that the set of usable

joinclauses is determined accurately for each join.  Formerly, the code only
considered joinclauses that used all of the rels from the outer side of the
join; thus for example
	FROM (a CROSS JOIN b) JOIN c ON (c.f1 = a.x AND c.f2 = b.y)
could not exploit a two-column index on c(f1,f2), since neither of the
qual clauses would be in the joininfo list it looked in.  The new code does
this correctly, and also is able to eliminate redundant clauses, thus fixing
the problem noted 24-Oct-02 by Hans-Jürgen Schönig.
parent 6bfc09ba
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* IDENTIFICATION * 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) ...@@ -1148,7 +1148,9 @@ _copyRelOptInfo(RelOptInfo *from)
newnode->baserestrictcost = from->baserestrictcost; newnode->baserestrictcost = from->baserestrictcost;
newnode->outerjoinset = listCopy(from->outerjoinset); newnode->outerjoinset = listCopy(from->outerjoinset);
Node_Copy(from, newnode, joininfo); 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; return newnode;
} }
...@@ -1200,6 +1202,9 @@ _copyIndexOptInfo(IndexOptInfo *from) ...@@ -1200,6 +1202,9 @@ _copyIndexOptInfo(IndexOptInfo *from)
Node_Copy(from, newnode, indpred); Node_Copy(from, newnode, indpred);
newnode->unique = from->unique; newnode->unique = from->unique;
newnode->outer_relids = listCopy(from->outer_relids);
Node_Copy(from, newnode, inner_paths);
return newnode; return newnode;
} }
...@@ -1262,8 +1267,6 @@ _copyIndexPath(IndexPath *from) ...@@ -1262,8 +1267,6 @@ _copyIndexPath(IndexPath *from)
Node_Copy(from, newnode, indexinfo); Node_Copy(from, newnode, indexinfo);
Node_Copy(from, newnode, indexqual); Node_Copy(from, newnode, indexqual);
newnode->indexscandir = from->indexscandir; newnode->indexscandir = from->indexscandir;
newnode->joinrelids = listCopy(from->joinrelids);
newnode->alljoinquals = from->alljoinquals;
newnode->rows = from->rows; newnode->rows = from->rows;
return newnode; return newnode;
...@@ -1491,6 +1494,25 @@ _copyJoinInfo(JoinInfo *from) ...@@ -1491,6 +1494,25 @@ _copyJoinInfo(JoinInfo *from)
return newnode; 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 * parsenodes.h copy functions
* **************************************************************** * ****************************************************************
...@@ -2952,6 +2974,9 @@ copyObject(void *from) ...@@ -2952,6 +2974,9 @@ copyObject(void *from)
case T_IndexOptInfo: case T_IndexOptInfo:
retval = _copyIndexOptInfo(from); retval = _copyIndexOptInfo(from);
break; break;
case T_InnerIndexscanInfo:
retval = _copyInnerIndexscanInfo(from);
break;
/* /*
* VALUE NODES * VALUE NODES
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* IDENTIFICATION * 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) ...@@ -429,10 +429,6 @@ _equalIndexPath(IndexPath *a, IndexPath *b)
return false; return false;
if (a->indexscandir != b->indexscandir) if (a->indexscandir != b->indexscandir)
return false; 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 * Skip 'rows' because of possibility of floating-point roundoff
...@@ -548,13 +544,11 @@ _equalRestrictInfo(RestrictInfo *a, RestrictInfo *b) ...@@ -548,13 +544,11 @@ _equalRestrictInfo(RestrictInfo *a, RestrictInfo *b)
return false; return false;
/* /*
* We ignore eval_cost, this_selec, left/right_pathkey, and * We ignore subclauseindices, eval_cost, this_selec, left/right_pathkey,
* left/right_bucketsize, since they may not be set yet, and should be * and left/right_bucketsize, since they may not be set yet, and should be
* derivable from the clause anyway. Probably it's not really * derivable from the clause anyway. Probably it's not really necessary
* necessary to compare any of these remaining fields ... * to compare any of these remaining fields ...
*/ */
if (!equal(a->subclauseindices, b->subclauseindices))
return false;
if (a->mergejoinoperator != b->mergejoinoperator) if (a->mergejoinoperator != b->mergejoinoperator)
return false; return false;
if (a->left_sortop != b->left_sortop) if (a->left_sortop != b->left_sortop)
...@@ -576,6 +570,18 @@ _equalJoinInfo(JoinInfo *a, JoinInfo *b) ...@@ -576,6 +570,18 @@ _equalJoinInfo(JoinInfo *a, JoinInfo *b)
return true; 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 * Stuff from parsenodes.h
*/ */
...@@ -2120,6 +2126,9 @@ equal(void *a, void *b) ...@@ -2120,6 +2126,9 @@ equal(void *a, void *b)
case T_JoinInfo: case T_JoinInfo:
retval = _equalJoinInfo(a, b); retval = _equalJoinInfo(a, b);
break; break;
case T_InnerIndexscanInfo:
retval = _equalInnerIndexscanInfo(a, b);
break;
case T_TidPath: case T_TidPath:
retval = _equalTidPath(a, b); retval = _equalTidPath(a, b);
break; break;
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 * NOTES
* XXX a few of the following functions are duplicated to handle * XXX a few of the following functions are duplicated to handle
...@@ -372,6 +372,46 @@ set_unioni(List *l1, List *l2) ...@@ -372,6 +372,46 @@ set_unioni(List *l1, List *l2)
return retval; return retval;
} }
/*
* 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() * member()
* nondestructive, returns t iff l1 is a member of the list l2 * nondestructive, returns t iff l1 is a member of the list l2
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 * NOTES
* Every (plan) node in POSTGRES has an associated "out" routine which * Every (plan) node in POSTGRES has an associated "out" routine which
...@@ -1067,12 +1067,8 @@ _outIndexPath(StringInfo str, IndexPath *node) ...@@ -1067,12 +1067,8 @@ _outIndexPath(StringInfo str, IndexPath *node)
appendStringInfo(str, " :indexqual "); appendStringInfo(str, " :indexqual ");
_outNode(str, node->indexqual); _outNode(str, node->indexqual);
appendStringInfo(str, " :indexscandir %d :joinrelids ", appendStringInfo(str, " :indexscandir %d :rows %.2f ",
(int) node->indexscandir); (int) node->indexscandir,
_outIntList(str, node->joinrelids);
appendStringInfo(str, " :alljoinquals %s :rows %.2f ",
booltostr(node->alljoinquals),
node->rows); node->rows);
} }
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 * NOTES
* Most of the read functions for plan nodes are tested. (In fact, they * Most of the read functions for plan nodes are tested. (In fact, they
...@@ -1824,13 +1824,6 @@ _readIndexPath(void) ...@@ -1824,13 +1824,6 @@ _readIndexPath(void)
token = pg_strtok(&length); /* now read it */ token = pg_strtok(&length); /* now read it */
local_node->indexscandir = (ScanDirection) atoi(token); 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); /* get :rows */
token = pg_strtok(&length); /* now read it */ token = pg_strtok(&length); /* now read it */
local_node->rows = atof(token); local_node->rows = atof(token);
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
* *
* *
* IDENTIFICATION * 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 @@ ...@@ -46,15 +46,11 @@
/* /*
* DoneMatchingIndexKeys() - MACRO * DoneMatchingIndexKeys() - MACRO
* *
* Determine whether we should continue matching index keys in a clause. * Formerly this looked at indexkeys, but that's the wrong thing for a
* Depends on if there are more to match or if this is a functional index. * 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
*/ */
#define DoneMatchingIndexKeys(indexkeys, index) \ #define DoneMatchingIndexKeys(indexkeys, classes) \
(indexkeys[0] == 0 || \ (classes[0] == InvalidOid)
(index->indproc != InvalidOid))
#define is_indexable_operator(clause,opclass,indexkey_on_left) \ #define is_indexable_operator(clause,opclass,indexkey_on_left) \
(indexable_operator(clause,opclass,indexkey_on_left) != InvalidOid) (indexable_operator(clause,opclass,indexkey_on_left) != InvalidOid)
...@@ -68,28 +64,25 @@ static List *match_index_orclause(RelOptInfo *rel, IndexOptInfo *index, ...@@ -68,28 +64,25 @@ static List *match_index_orclause(RelOptInfo *rel, IndexOptInfo *index,
static bool match_or_subclause_to_indexkey(RelOptInfo *rel, static bool match_or_subclause_to_indexkey(RelOptInfo *rel,
IndexOptInfo *index, IndexOptInfo *index,
Expr *clause); Expr *clause);
static List *group_clauses_by_indexkey(RelOptInfo *rel, IndexOptInfo *index, static List *group_clauses_by_indexkey(RelOptInfo *rel, IndexOptInfo *index);
int *indexkeys, Oid *classes, static List *group_clauses_by_indexkey_for_join(RelOptInfo *rel,
List *restrictinfo_list);
static List *group_clauses_by_ikey_for_joins(RelOptInfo *rel,
IndexOptInfo *index, IndexOptInfo *index,
int *indexkeys, Oid *classes, Relids outer_relids,
List *join_cinfo_list, bool isouterjoin);
List *restr_cinfo_list);
static bool match_clause_to_indexkey(RelOptInfo *rel, IndexOptInfo *index, static bool match_clause_to_indexkey(RelOptInfo *rel, IndexOptInfo *index,
int indexkey, Oid opclass, int indexkey, Oid opclass, Expr *clause);
Expr *clause, bool join); 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, static bool pred_test(List *predicate_list, List *restrictinfo_list,
List *joininfo_list, int relvarno); List *joininfo_list, int relvarno);
static bool pred_test_restrict_list(Expr *predicate, List *restrictinfo_list); 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_clause(Expr *predicate, Node *clause);
static bool pred_test_recurse_pred(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 bool pred_test_simple_clause(Expr *predicate, Node *clause);
static void indexable_joinclauses(RelOptInfo *rel, IndexOptInfo *index, static Relids indexable_outerrelids(RelOptInfo *rel, IndexOptInfo *index);
List *joininfo_list, List *restrictinfo_list, static Path *make_innerjoin_index_path(Query *root,
List **clausegroups, List **outerrelids); RelOptInfo *rel, IndexOptInfo *index,
static List *index_innerjoin(Query *root, RelOptInfo *rel, IndexOptInfo *index, List *clausegroup);
List *clausegroup_list, List *outerrelids_list);
static bool match_index_to_operand(int indexkey, Var *operand, static bool match_index_to_operand(int indexkey, Var *operand,
RelOptInfo *rel, IndexOptInfo *index); RelOptInfo *rel, IndexOptInfo *index);
static bool function_index_operand(Expr *funcOpnd, RelOptInfo *rel, static bool function_index_operand(Expr *funcOpnd, RelOptInfo *rel,
...@@ -108,7 +101,6 @@ static Const *string_to_const(const char *str, Oid datatype); ...@@ -108,7 +101,6 @@ static Const *string_to_const(const char *str, Oid datatype);
* create_index_paths() * create_index_paths()
* Generate all interesting index paths for the given relation. * Generate all interesting index paths for the given relation.
* Candidate paths are added to the rel's pathlist (using add_path). * 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 * 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, * 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); ...@@ -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' * in its join clauses. In that context, values for the other rels'
* attributes are available and fixed during any one scan of the indexpath. * attributes are available and fixed during any one scan of the indexpath.
* *
* An IndexPath is generated and submitted to add_path() for each index * An IndexPath is generated and submitted to add_path() for each plain index
* this routine deems potentially interesting for the current query. * scan 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(), * We also determine the set of other relids that participate in join
* but are appended to the "innerjoin" list of the relation for later * clauses that could be used with each index. The actually best innerjoin
* consideration in nested-loop joins. * 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 * 'rel' is the relation for which we want to generate index paths
*/ */
...@@ -137,6 +131,7 @@ create_index_paths(Query *root, RelOptInfo *rel) ...@@ -137,6 +131,7 @@ create_index_paths(Query *root, RelOptInfo *rel)
{ {
List *restrictinfo_list = rel->baserestrictinfo; List *restrictinfo_list = rel->baserestrictinfo;
List *joininfo_list = rel->joininfo; List *joininfo_list = rel->joininfo;
Relids all_join_outerrelids = NIL;
List *ilist; List *ilist;
foreach(ilist, rel->indexlist) foreach(ilist, rel->indexlist)
...@@ -146,8 +141,7 @@ create_index_paths(Query *root, RelOptInfo *rel) ...@@ -146,8 +141,7 @@ create_index_paths(Query *root, RelOptInfo *rel)
List *index_pathkeys; List *index_pathkeys;
List *useful_pathkeys; List *useful_pathkeys;
bool index_is_ordered; bool index_is_ordered;
List *joinclausegroups; Relids join_outerrelids;
List *joinouterrelids;
/* /*
* If this is a partial index, we can only use it if it passes the * 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) ...@@ -178,11 +172,7 @@ create_index_paths(Query *root, RelOptInfo *rel)
/* /*
* 2. Match the index against non-'or' restriction clauses. * 2. Match the index against non-'or' restriction clauses.
*/ */
restrictclauses = group_clauses_by_indexkey(rel, restrictclauses = group_clauses_by_indexkey(rel, index);
index,
index->indexkeys,
index->classlist,
restrictinfo_list);
/* /*
* 3. Compute pathkeys describing index's ordering, if any, then * 3. Compute pathkeys describing index's ordering, if any, then
...@@ -234,26 +224,19 @@ create_index_paths(Query *root, RelOptInfo *rel) ...@@ -234,26 +224,19 @@ create_index_paths(Query *root, RelOptInfo *rel)
} }
/* /*
* 6. Create an innerjoin index path for each combination of other * 6. Examine join clauses to see which ones are potentially
* rels used in available join clauses. These paths will be * usable with this index, and generate a list of all other relids
* considered as the inner side of nestloop joins against those * that participate in such join clauses. We'll use this list later
* sets of other rels. indexable_joinclauses() finds sets of * to recognize outer rels that are equivalent for joining purposes.
* clauses that can be used with each combination of outer rels, * We compute both per-index and overall-for-relation lists.
* and index_innerjoin builds the paths themselves. We add the
* paths to the rel's innerjoin list, NOT to the result list.
*/ */
indexable_joinclauses(rel, index, join_outerrelids = indexable_outerrelids(rel, index);
joininfo_list, restrictinfo_list, index->outer_relids = join_outerrelids;
&joinclausegroups, all_join_outerrelids = set_unioni(all_join_outerrelids,
&joinouterrelids); join_outerrelids);
if (joinclausegroups != NIL)
{
rel->innerjoin = nconc(rel->innerjoin,
index_innerjoin(root, rel, index,
joinclausegroups,
joinouterrelids));
}
} }
rel->index_outer_relids = all_join_outerrelids;
} }
...@@ -397,14 +380,14 @@ match_or_subclause_to_indexkey(RelOptInfo *rel, ...@@ -397,14 +380,14 @@ match_or_subclause_to_indexkey(RelOptInfo *rel,
foreach(item, clause->args) foreach(item, clause->args)
{ {
if (match_clause_to_indexkey(rel, index, indexkey, opclass, if (match_clause_to_indexkey(rel, index, indexkey, opclass,
lfirst(item), false)) lfirst(item)))
return true; return true;
} }
return false; return false;
} }
else else
return match_clause_to_indexkey(rel, index, indexkey, opclass, return match_clause_to_indexkey(rel, index, indexkey, opclass,
clause, false); clause);
} }
/*---------- /*----------
...@@ -466,13 +449,13 @@ extract_or_indexqual_conditions(RelOptInfo *rel, ...@@ -466,13 +449,13 @@ extract_or_indexqual_conditions(RelOptInfo *rel,
if (match_clause_to_indexkey(rel, index, if (match_clause_to_indexkey(rel, index,
curIndxKey, curClass, curIndxKey, curClass,
subsubclause, false)) subsubclause))
clausegroup = lappend(clausegroup, subsubclause); clausegroup = lappend(clausegroup, subsubclause);
} }
} }
else if (match_clause_to_indexkey(rel, index, else if (match_clause_to_indexkey(rel, index,
curIndxKey, curClass, curIndxKey, curClass,
orsubclause, false)) orsubclause))
clausegroup = makeList1(orsubclause); clausegroup = makeList1(orsubclause);
/* /*
...@@ -487,7 +470,7 @@ extract_or_indexqual_conditions(RelOptInfo *rel, ...@@ -487,7 +470,7 @@ extract_or_indexqual_conditions(RelOptInfo *rel,
if (match_clause_to_indexkey(rel, index, if (match_clause_to_indexkey(rel, index,
curIndxKey, curClass, curIndxKey, curClass,
rinfo->clause, false)) rinfo->clause))
clausegroup = lappend(clausegroup, rinfo->clause); clausegroup = lappend(clausegroup, rinfo->clause);
} }
} }
...@@ -503,7 +486,8 @@ extract_or_indexqual_conditions(RelOptInfo *rel, ...@@ -503,7 +486,8 @@ extract_or_indexqual_conditions(RelOptInfo *rel,
indexkeys++; indexkeys++;
classes++; classes++;
} while (!DoneMatchingIndexKeys(indexkeys, index));
} while (!DoneMatchingIndexKeys(indexkeys, classes));
if (quals == NIL) if (quals == NIL)
elog(ERROR, "extract_or_indexqual_conditions: no matching clause"); elog(ERROR, "extract_or_indexqual_conditions: no matching clause");
...@@ -523,15 +507,12 @@ extract_or_indexqual_conditions(RelOptInfo *rel, ...@@ -523,15 +507,12 @@ extract_or_indexqual_conditions(RelOptInfo *rel,
* *
* 'rel' is the node of the relation itself. * 'rel' is the node of the relation itself.
* 'index' is a index on 'rel'. * '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 * Returns a list of all the RestrictInfo nodes for clauses that can be
* used with this index. * used with this index.
* *
* The list is ordered by index key. (This is not depended on by any part * 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 * do assume that the indxqual list ultimately delivered to the executor
* is so ordered. One such place is _bt_orderkeys() in the btree support. * is so ordered. One such place is _bt_orderkeys() in the btree support.
* Perhaps that ought to be fixed someday --- tgl 7/00) * Perhaps that ought to be fixed someday --- tgl 7/00)
...@@ -544,15 +525,14 @@ extract_or_indexqual_conditions(RelOptInfo *rel, ...@@ -544,15 +525,14 @@ extract_or_indexqual_conditions(RelOptInfo *rel,
* clauses matching column C, because the executor couldn't use them anyway. * clauses matching column C, because the executor couldn't use them anyway.
*/ */
static List * static List *
group_clauses_by_indexkey(RelOptInfo *rel, group_clauses_by_indexkey(RelOptInfo *rel, IndexOptInfo *index)
IndexOptInfo *index,
int *indexkeys,
Oid *classes,
List *restrictinfo_list)
{ {
List *clausegroup_list = NIL; 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; return NIL;
do do
...@@ -560,18 +540,17 @@ group_clauses_by_indexkey(RelOptInfo *rel, ...@@ -560,18 +540,17 @@ group_clauses_by_indexkey(RelOptInfo *rel,
int curIndxKey = indexkeys[0]; int curIndxKey = indexkeys[0];
Oid curClass = classes[0]; Oid curClass = classes[0];
List *clausegroup = NIL; 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, if (match_clause_to_indexkey(rel,
index, index,
curIndxKey, curIndxKey,
curClass, curClass,
rinfo->clause, rinfo->clause))
false))
clausegroup = lappend(clausegroup, rinfo); clausegroup = lappend(clausegroup, rinfo);
} }
...@@ -587,71 +566,84 @@ group_clauses_by_indexkey(RelOptInfo *rel, ...@@ -587,71 +566,84 @@ group_clauses_by_indexkey(RelOptInfo *rel,
indexkeys++; indexkeys++;
classes++; classes++;
} while (!DoneMatchingIndexKeys(indexkeys, index)); } while (!DoneMatchingIndexKeys(indexkeys, classes));
/* clausegroup_list holds all matched clauses ordered by indexkeys */ /* clausegroup_list holds all matched clauses ordered by indexkeys */
return clausegroup_list; return clausegroup_list;
} }
/* /*
* group_clauses_by_ikey_for_joins * group_clauses_by_indexkey_for_join
* Generates a list of join clauses that can be used with an index * Generates a list of clauses that can be used with an index
* to scan the inner side of a nestloop join. * to scan the inner side of a nestloop join.
* *
* This is much like group_clauses_by_indexkey(), but we consider both * This is much like group_clauses_by_indexkey(), but we consider both
* join and restriction clauses. For each indexkey in the index, we * join and restriction clauses. Any joinclause that uses only otherrels
* accept both join and restriction clauses that match it, since both * in the specified outer_relids is fair game. But there must be at least
* will make useful indexquals if the index is being used to scan the * one such joinclause in the final list, otherwise we return NIL indicating
* inner side of a nestloop join. But there must be at least one matching * that this index isn't interesting as an inner indexscan. (A scan using
* join clause, or we return NIL indicating that this index isn't useful * only restriction clauses shouldn't be created here, because a regular Path
* for nestloop joining. * will already have been generated for it.)
*/ */
static List * static List *
group_clauses_by_ikey_for_joins(RelOptInfo *rel, group_clauses_by_indexkey_for_join(RelOptInfo *rel, IndexOptInfo *index,
IndexOptInfo *index, Relids outer_relids, bool isouterjoin)
int *indexkeys,
Oid *classes,
List *join_cinfo_list,
List *restr_cinfo_list)
{ {
List *clausegroup_list = NIL; List *clausegroup_list = NIL;
bool jfound = false; bool jfound = false;
int *indexkeys = index->indexkeys;
if (join_cinfo_list == NIL || indexkeys[0] == 0) Oid *classes = index->classlist;
return NIL;
do do
{ {
int curIndxKey = indexkeys[0]; int curIndxKey = indexkeys[0];
Oid curClass = classes[0]; Oid curClass = classes[0];
List *clausegroup = NIL; 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, if (!is_subseti(joininfo->unjoined_relids, outer_relids))
index, continue;
curIndxKey,
curClass, foreach(j, joininfo->jinfo_restrictinfo)
rinfo->clause,
true))
{ {
clausegroup = lappend(clausegroup, rinfo); RestrictInfo *rinfo = (RestrictInfo *) lfirst(j);
jfound = true;
/* 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, if (match_clause_to_indexkey(rel,
index, index,
curIndxKey, curIndxKey,
curClass, curClass,
rinfo->clause, rinfo->clause))
false))
clausegroup = lappend(clausegroup, rinfo); clausegroup = lappend(clausegroup, rinfo);
} }
...@@ -667,11 +659,10 @@ group_clauses_by_ikey_for_joins(RelOptInfo *rel, ...@@ -667,11 +659,10 @@ group_clauses_by_ikey_for_joins(RelOptInfo *rel,
indexkeys++; indexkeys++;
classes++; classes++;
} while (!DoneMatchingIndexKeys(indexkeys, index)); } while (!DoneMatchingIndexKeys(indexkeys, classes));
/* /*
* if no join clause was matched then there ain't clauses for joins at * if no join clause was matched then forget it, per comments above.
* all.
*/ */
if (!jfound) if (!jfound)
{ {
...@@ -686,16 +677,12 @@ group_clauses_by_ikey_for_joins(RelOptInfo *rel, ...@@ -686,16 +677,12 @@ group_clauses_by_ikey_for_joins(RelOptInfo *rel,
/* /*
* match_clause_to_indexkey() * match_clause_to_indexkey()
* Determines whether a restriction or join clause matches * Determines whether a restriction clause matches a key of an index.
* a key of an index.
* *
* To match, the clause: * To match, the clause:
* *
* (1a) for a restriction clause: must be in the form (indexkey op const) * (1) must be in the form (indexkey op const) or (const op indexkey);
* or (const op indexkey), or * and
* (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
* (2) must contain an operator which is in the same class as the index * (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 * operator for this key, or is a "special" operator as recognized
* by match_special_index_operator(). * by match_special_index_operator().
...@@ -706,18 +693,11 @@ group_clauses_by_ikey_for_joins(RelOptInfo *rel, ...@@ -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 * We do not actually do the commuting here, but we check whether a
* suitable commutator operator is available. * 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. * 'rel' is the relation of interest.
* 'index' is an index on 'rel'. * 'index' is an index on 'rel'.
* 'indexkey' is a key of 'index'. * 'indexkey' is a key of 'index'.
* 'opclass' is the corresponding operator class. * 'opclass' is the corresponding operator class.
* 'clause' is the clause to be tested. * '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. * Returns true if the clause can be used with this index key.
* *
...@@ -729,8 +709,7 @@ match_clause_to_indexkey(RelOptInfo *rel, ...@@ -729,8 +709,7 @@ match_clause_to_indexkey(RelOptInfo *rel,
IndexOptInfo *index, IndexOptInfo *index,
int indexkey, int indexkey,
Oid opclass, Oid opclass,
Expr *clause, Expr *clause)
bool join)
{ {
Var *leftop, Var *leftop,
*rightop; *rightop;
...@@ -743,75 +722,124 @@ match_clause_to_indexkey(RelOptInfo *rel, ...@@ -743,75 +722,124 @@ match_clause_to_indexkey(RelOptInfo *rel,
if (!leftop || !rightop) if (!leftop || !rightop)
return false; 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: * If we didn't find a member of the index's opclass, see
* (indexkey operator constant) or (constant operator indexkey). * whether it is a "special" indexable operator.
* Anything that is a "pseudo constant" expression will do.
*/ */
if (match_special_index_operator(clause, opclass, true))
return true;
return false;
}
if (match_index_to_operand(indexkey, leftop, rel, index) && if (match_index_to_operand(indexkey, rightop, rel, index) &&
is_pseudo_constant_clause((Node *) rightop)) is_pseudo_constant_clause((Node *) leftop))
{ {
if (is_indexable_operator(clause, opclass, true)) if (is_indexable_operator(clause, opclass, false))
return true; return true;
/* /*
* If we didn't find a member of the index's opclass, see * If we didn't find a member of the index's opclass, see
* whether it is a "special" indexable operator. * whether it is a "special" indexable operator.
*/ */
if (match_special_index_operator(clause, opclass, true)) if (match_special_index_operator(clause, opclass, false))
return true; return true;
return false; 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;
/* 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)) * match_join_clause_to_indexkey()
return true; * Determines whether a join clause matches a key of an index.
return false; *
} * 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))
{ {
/* List *othervarnos = pull_varnos((Node *) leftop);
* Check for an indexqual that could be handled by a nestloop bool isIndexable;
* join. We need the index key to be compared against an
* expression that uses none of the indexed relation's vars and isIndexable =
* contains no volatile functions. !intMember(lfirsti(rel->relids), othervarnos) &&
*/ !contain_volatile_functions((Node *) leftop) &&
if (match_index_to_operand(indexkey, leftop, rel, index)) is_indexable_operator(clause, opclass, false);
{ freeList(othervarnos);
List *othervarnos = pull_varnos((Node *) rightop); return isIndexable;
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;
}
} }
return false; return false;
...@@ -1267,164 +1295,288 @@ pred_test_simple_clause(Expr *predicate, Node *clause) ...@@ -1267,164 +1295,288 @@ pred_test_simple_clause(Expr *predicate, Node *clause)
****************************************************************************/ ****************************************************************************/
/* /*
* indexable_joinclauses * indexable_outerrelids
* Finds all groups of join clauses from among 'joininfo_list' that can * Finds all other relids that participate in any indexable join clause
* be used in conjunction with 'index' for the inner scan of a nestjoin. * for the specified index. Returns a list of relids.
*
* 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.
* *
* 'rel' is the relation for which 'index' is defined * '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 static Relids
indexable_joinclauses(RelOptInfo *rel, IndexOptInfo *index, indexable_outerrelids(RelOptInfo *rel, IndexOptInfo *index)
List *joininfo_list, List *restrictinfo_list,
List **clausegroups, List **outerrelids)
{ {
List *cg_list = NIL; Relids outer_relids = NIL;
List *relid_list = NIL;
List *i; List *i;
foreach(i, joininfo_list) foreach(i, rel->joininfo)
{ {
JoinInfo *joininfo = (JoinInfo *) lfirst(i); 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, } while (!DoneMatchingIndexKeys(indexkeys, classes));
index,
index->indexkeys,
index->classlist,
joininfo->jinfo_restrictinfo,
restrictinfo_list);
if (clausegroup != NIL) if (match_found)
break;
}
if (match_found)
{ {
cg_list = lappend(cg_list, clausegroup); outer_relids = set_unioni(outer_relids,
relid_list = lappend(relid_list, joininfo->unjoined_relids); joininfo->unjoined_relids);
} }
} }
*clausegroups = cg_list; return outer_relids;
*outerrelids = relid_list;
} }
/****************************************************************************
* ---- PATH CREATION UTILITIES ----
****************************************************************************/
/* /*
* index_innerjoin * best_inner_indexscan
* Creates index path nodes corresponding to paths to be used as inner * Finds the best available inner indexscan for a nestloop join
* relations in nestloop joins. * with the given rel on the inside and the given outer_relids outside.
* * May return NULL if there are no possible inner indexscans.
* '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.
* *
* 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 * Path *
index_innerjoin(Query *root, RelOptInfo *rel, IndexOptInfo *index, best_inner_indexscan(Query *root, RelOptInfo *rel,
List *clausegroup_list, List *outerrelids_list) Relids outer_relids, JoinType jointype)
{ {
List *path_list = NIL; Path *cheapest = NULL;
List *i; 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); case JOIN_INNER:
IndexPath *pathnode = makeNode(IndexPath); isouterjoin = false;
List *indexquals = NIL; break;
bool alljoinquals = true; case JOIN_LEFT:
List *temp; isouterjoin = true;
break;
/* XXX this code ought to be merged with create_index_path? */ default:
return NULL;
pathnode->path.pathtype = T_IndexScan; }
pathnode->path.parent = rel; /*
* 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 * Look to see if we already computed the result for this index.
* it will only ever be used as the inner path of a nestloop, and
* so its ordering does not matter.
*/ */
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 */ if (jlist == NIL) /* failed to find a match? */
foreach(temp, clausegroup)
{ {
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); /* Cache the result --- whether positive or negative */
if (clause->ispusheddown) info = makeNode(InnerIndexscanInfo);
alljoinquals = false; 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 */ if (path != NULL &&
indexquals = expand_indexqual_conditions(indexquals); (cheapest == NULL ||
compare_path_costs(path, cheapest, TOTAL_COST) < 0))
cheapest = path;
}
/* /* Cache the result --- whether positive or negative */
* Note that we are making a pathnode for a single-scan indexscan; info = makeNode(InnerIndexscanInfo);
* therefore, both indexinfo and indexqual should be info->other_relids = outer_relids;
* single-element lists. info->isouterjoin = isouterjoin;
*/ info->best_innerpath = cheapest;
pathnode->indexinfo = makeList1(index); rel->index_inner_paths = lcons(info, rel->index_inner_paths);
pathnode->indexqual = makeList1(indexquals);
/* We don't actually care what order the index scans in ... */ return cheapest;
pathnode->indexscandir = NoMovementScanDirection; }
/* 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;
/* /* XXX this code ought to be merged with create_index_path? */
* We must compute the estimated number of output rows for the
* indexscan. This is less than rel->rows because of the pathnode->path.pathtype = T_IndexScan;
* additional selectivity of the join clauses. Since clausegroup pathnode->path.parent = rel;
* 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 * There's no point in marking the path with any pathkeys, since
* nconc the two lists; then we might have some restriction * it will only ever be used as the inner path of a nestloop, and
* clauses appearing twice, which'd mislead * so its ordering does not matter.
* restrictlist_selectivity into double-counting their */
* selectivity.) pathnode->path.pathkeys = NIL;
*/
pathnode->rows = rel->tuples * /* Extract bare indexqual clauses from restrictinfos */
restrictlist_selectivity(root, indexquals = get_actual_clauses(clausegroup);
set_union(rel->baserestrictinfo,
clausegroup), /* expand special operators to indexquals the executor can handle */
lfirsti(rel->relids)); indexquals = expand_indexqual_conditions(indexquals);
/* Like costsize.c, force estimate to be at least one row */
if (pathnode->rows < 1.0) /*
pathnode->rows = 1.0; * Note that we are making a pathnode for a single-scan indexscan;
* therefore, both indexinfo and indexqual should be single-element lists.
cost_index(&pathnode->path, root, rel, index, indexquals, true); */
pathnode->indexinfo = makeList1(index);
path_list = lappend(path_list, pathnode); pathnode->indexqual = makeList1(indexquals);
outerrelids_list = lnext(outerrelids_list);
} /* We don't actually care what order the index scans in ... */
return path_list; 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;
} }
/**************************************************************************** /****************************************************************************
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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, ...@@ -42,8 +42,6 @@ static void match_unsorted_inner(Query *root, RelOptInfo *joinrel,
static void hash_inner_and_outer(Query *root, RelOptInfo *joinrel, static void hash_inner_and_outer(Query *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel, RelOptInfo *outerrel, RelOptInfo *innerrel,
List *restrictlist, JoinType jointype); List *restrictlist, JoinType jointype);
static Path *best_innerjoin(List *join_paths, List *outer_relid,
JoinType jointype);
static List *select_mergejoin_clauses(RelOptInfo *joinrel, static List *select_mergejoin_clauses(RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *outerrel,
RelOptInfo *innerrel, RelOptInfo *innerrel,
...@@ -351,8 +349,8 @@ match_unsorted_outer(Query *root, ...@@ -351,8 +349,8 @@ match_unsorted_outer(Query *root,
* Get the best innerjoin indexpath (if any) for this outer rel. It's * Get the best innerjoin indexpath (if any) for this outer rel. It's
* the same for all outer paths. * the same for all outer paths.
*/ */
bestinnerjoin = best_innerjoin(innerrel->innerjoin, outerrel->relids, bestinnerjoin = best_inner_indexscan(root, innerrel,
jointype); outerrel->relids, jointype);
foreach(i, outerrel->pathlist) foreach(i, outerrel->pathlist)
{ {
...@@ -812,69 +810,6 @@ hash_inner_and_outer(Query *root, ...@@ -812,69 +810,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
* Select mergejoin clauses that are usable for a particular join. * Select mergejoin clauses that are usable for a particular join.
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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) ...@@ -92,9 +92,6 @@ create_or_index_paths(Query *root, RelOptInfo *rel)
/* We don't actually care what order the index scans in. */ /* We don't actually care what order the index scans in. */
pathnode->indexscandir = NoMovementScanDirection; pathnode->indexscandir = NoMovementScanDirection;
/* This isn't a nestloop innerjoin, so: */
pathnode->joinrelids = NIL; /* no join clauses here */
pathnode->alljoinquals = false;
pathnode->rows = rel->rows; pathnode->rows = rel->rows;
best_or_subclause_indices(root, best_or_subclause_indices(root,
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
* *
* *
* IDENTIFICATION * 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 @@ ...@@ -25,7 +25,6 @@
#include "parser/parse_coerce.h" #include "parser/parse_coerce.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
static void create_tidscan_joinpaths(Query *root, RelOptInfo *rel);
static List *TidqualFromRestrictinfo(List *relids, List *restrictinfo); static List *TidqualFromRestrictinfo(List *relids, List *restrictinfo);
static bool isEvaluable(int varno, Node *node); static bool isEvaluable(int varno, Node *node);
static Node *TidequalClause(int varno, Expr *node); static Node *TidequalClause(int varno, Expr *node);
...@@ -236,44 +235,6 @@ TidqualFromRestrictinfo(List *relids, List *restrictinfo) ...@@ -236,44 +235,6 @@ TidqualFromRestrictinfo(List *relids, List *restrictinfo)
return rlst; return rlst;
} }
/*
* 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 * create_tidscan_paths
* Creates paths corresponding to tid direct scans of the given rel. * Creates paths corresponding to tid direct scans of the given rel.
...@@ -287,5 +248,4 @@ create_tidscan_paths(Query *root, RelOptInfo *rel) ...@@ -287,5 +248,4 @@ create_tidscan_paths(Query *root, RelOptInfo *rel)
if (tideval) if (tideval)
add_path(rel, (Path *) create_tidscan_path(root, rel, tideval)); add_path(rel, (Path *) create_tidscan_path(root, rel, tideval));
create_tidscan_joinpaths(root, rel);
} }
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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, ...@@ -577,15 +577,12 @@ distribute_qual_to_rels(Query *root, Node *clause,
* the relid list. Set additional RestrictInfo fields for * the relid list. Set additional RestrictInfo fields for
* joining. * joining.
* *
* We don't bother setting the merge/hashjoin info if we're not going * 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 any * to need it. We do want to know about mergejoinable ops in all
* potential equijoin clause (see later in this routine), and we * cases, however, because we use mergejoinable ops for other
* ignore enable_mergejoin if isouterjoin is true, because * purposes such as detecting redundant clauses.
* mergejoin is the only implementation we have for full and right
* outer joins.
*/ */
if (enable_mergejoin || isouterjoin || can_be_equijoin) check_mergejoinable(restrictinfo);
check_mergejoinable(restrictinfo);
if (enable_hashjoin) if (enable_hashjoin)
check_hashjoinable(restrictinfo); check_hashjoinable(restrictinfo);
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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, ...@@ -354,12 +354,9 @@ create_index_path(Query *root,
pathnode->indexscandir = indexscandir; pathnode->indexscandir = indexscandir;
/* /*
* This routine is only used to generate "standalone" indexpaths, not * The number of rows is the same as the parent rel's estimate, since
* nestloop inner indexpaths. So joinrelids is always NIL and the * this isn't a join inner indexscan.
* number of rows is the same as the parent rel's estimate.
*/ */
pathnode->joinrelids = NIL; /* no join clauses here */
pathnode->alljoinquals = false;
pathnode->rows = rel->rows; pathnode->rows = rel->rows;
/* /*
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
* *
* *
* IDENTIFICATION * 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) ...@@ -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); index_close(indexRelation);
indexinfos = lcons(info, indexinfos); indexinfos = lcons(info, indexinfos);
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 @@ ...@@ -17,8 +17,8 @@
#include "optimizer/cost.h" #include "optimizer/cost.h"
#include "optimizer/joininfo.h" #include "optimizer/joininfo.h"
#include "optimizer/pathnode.h" #include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/plancat.h" #include "optimizer/plancat.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/tlist.h" #include "optimizer/tlist.h"
#include "parser/parsetree.h" #include "parser/parsetree.h"
...@@ -152,7 +152,8 @@ make_base_rel(Query *root, int relid) ...@@ -152,7 +152,8 @@ make_base_rel(Query *root, int relid)
rel->baserestrictcost = 0; rel->baserestrictcost = 0;
rel->outerjoinset = NIL; rel->outerjoinset = NIL;
rel->joininfo = NIL; rel->joininfo = NIL;
rel->innerjoin = NIL; rel->index_outer_relids = NIL;
rel->index_inner_paths = NIL;
/* Check type of rtable entry */ /* Check type of rtable entry */
switch (rte->rtekind) switch (rte->rtekind)
...@@ -365,7 +366,8 @@ build_join_rel(Query *root, ...@@ -365,7 +366,8 @@ build_join_rel(Query *root,
joinrel->baserestrictcost = 0; joinrel->baserestrictcost = 0;
joinrel->outerjoinset = NIL; joinrel->outerjoinset = NIL;
joinrel->joininfo = 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? */ /* Is there a join RTE matching this join? */
joinrterel = find_other_rel_for_join(root, joinrelids); joinrterel = find_other_rel_for_join(root, joinrelids);
...@@ -529,9 +531,8 @@ build_joinrel_restrictlist(Query *root, ...@@ -529,9 +531,8 @@ build_joinrel_restrictlist(Query *root,
RelOptInfo *inner_rel, RelOptInfo *inner_rel,
JoinType jointype) JoinType jointype)
{ {
List *result = NIL; List *result;
List *rlist; List *rlist;
List *item;
/* /*
* Collect all the clauses that syntactically belong at this level. * Collect all the clauses that syntactically belong at this level.
...@@ -549,59 +550,8 @@ build_joinrel_restrictlist(Query *root, ...@@ -549,59 +550,8 @@ build_joinrel_restrictlist(Query *root,
* mergejoinable clause, it's possible that it is redundant with * mergejoinable clause, it's possible that it is redundant with
* previous clauses (see optimizer/README for discussion). We detect * previous clauses (see optimizer/README for discussion). We detect
* that case and omit the redundant clause from the result list. * 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) result = remove_redundant_join_clauses(root, rlist, jointype);
{
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);
}
freeList(rlist); freeList(rlist);
......
...@@ -8,21 +8,21 @@ ...@@ -8,21 +8,21 @@
* *
* *
* IDENTIFICATION * 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 "postgres.h"
#include "optimizer/clauses.h" #include "optimizer/clauses.h"
#include "optimizer/paths.h"
#include "optimizer/restrictinfo.h" #include "optimizer/restrictinfo.h"
/* /*
* restriction_is_or_clause * restriction_is_or_clause
* *
* Returns t iff the restrictinfo node contains an 'or' clause. * Returns t iff the restrictinfo node contains an 'or' clause.
*
*/ */
bool bool
restriction_is_or_clause(RestrictInfo *restrictinfo) restriction_is_or_clause(RestrictInfo *restrictinfo)
...@@ -37,8 +37,7 @@ restriction_is_or_clause(RestrictInfo *restrictinfo) ...@@ -37,8 +37,7 @@ restriction_is_or_clause(RestrictInfo *restrictinfo)
/* /*
* get_actual_clauses * get_actual_clauses
* *
* Returns a list containing the clauses from 'restrictinfo_list'. * Returns a list containing the bare clauses from 'restrictinfo_list'.
*
*/ */
List * List *
get_actual_clauses(List *restrictinfo_list) get_actual_clauses(List *restrictinfo_list)
...@@ -80,3 +79,81 @@ get_actual_join_clauses(List *restrictinfo_list, ...@@ -80,3 +79,81 @@ get_actual_join_clauses(List *restrictinfo_list,
*joinquals = lappend(*joinquals, clause->clause); *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;
}
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 ...@@ -87,6 +87,7 @@ typedef enum NodeTag
T_RestrictInfo, T_RestrictInfo,
T_JoinInfo, T_JoinInfo,
T_IndexOptInfo, T_IndexOptInfo,
T_InnerIndexscanInfo,
/* /*
* TAGS FOR EXECUTOR NODES (execnodes.h) * TAGS FOR EXECUTOR NODES (execnodes.h)
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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); ...@@ -141,6 +141,7 @@ extern List *set_differencei(List *list1, List *list2);
extern List *lreverse(List *l); extern List *lreverse(List *l);
extern List *set_union(List *list1, List *list2); extern List *set_union(List *list1, List *list2);
extern List *set_unioni(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 equali(List *list1, List *list2);
extern bool sameseti(List *list1, List *list2); extern bool sameseti(List *list1, List *list2);
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 ...@@ -129,9 +129,11 @@ typedef enum CostSelector
* syntactically within the join. Otherwise, unused. * syntactically within the join. Otherwise, unused.
* joininfo - List of JoinInfo nodes, containing info about each join * joininfo - List of JoinInfo nodes, containing info about each join
* clause in which this relation participates * clause in which this relation participates
* innerjoin - List of Path nodes that represent indices that may be used * index_outer_relids - only used for base rels; list of outer relids
* as inner paths of nestloop joins. This field is non-null * that participate in indexable joinclauses for this rel
* only for base rels, since join rels have no indices. * 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 * 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 * base rels, because for a join rel the set of clauses that are treated as
...@@ -200,12 +202,17 @@ typedef struct RelOptInfo ...@@ -200,12 +202,17 @@ typedef struct RelOptInfo
Cost baserestrictcost; /* cost of evaluating the above */ Cost baserestrictcost; /* cost of evaluating the above */
Relids outerjoinset; /* integer list of base relids */ Relids outerjoinset; /* integer list of base relids */
List *joininfo; /* JoinInfo structures */ 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 * Inner indexscans are not in the main pathlist because they are
* not usable except in specific join contexts; we have to test before * not usable except in specific join contexts. We use the
* seeing whether they can be used. * index_inner_paths list just to avoid recomputing the best inner
* indexscan repeatedly for similar outer relations. See comments
* for InnerIndexscanInfo.
*/ */
} RelOptInfo; } RelOptInfo;
...@@ -217,20 +224,6 @@ typedef struct RelOptInfo ...@@ -217,20 +224,6 @@ typedef struct RelOptInfo
* and indexes, but that created confusion without actually doing anything * and indexes, but that created confusion without actually doing anything
* useful. So now we have a separate IndexOptInfo struct for indexes. * 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, * ncolumns and nkeys are the same except for a functional index,
* wherein ncolumns is 1 (the single function output) while nkeys * wherein ncolumns is 1 (the single function output) while nkeys
* is the number of table columns passed to the function. classlist[] * is the number of table columns passed to the function. classlist[]
...@@ -249,22 +242,26 @@ typedef struct IndexOptInfo ...@@ -249,22 +242,26 @@ typedef struct IndexOptInfo
Oid indexoid; /* OID of the index relation */ Oid indexoid; /* OID of the index relation */
/* statistics from pg_class */ /* statistics from pg_class */
long pages; long pages; /* number of disk pages in index */
double tuples; double tuples; /* number of index tuples in index */
/* index descriptor information */ /* index descriptor information */
int ncolumns; /* number of columns in index */ int ncolumns; /* number of columns in index */
int nkeys; /* number of keys used by 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 */ int *indexkeys; /* column numbers of index's keys */
Oid *ordering; /* OIDs of sort operators for each column */ Oid *ordering; /* OIDs of sort operators for each column */
Oid relam; /* OID of the access method (in pg_am) */ Oid relam; /* OID of the access method (in pg_am) */
RegProcedure amcostestimate; /* OID of the access method's cost fcn */ RegProcedure amcostestimate; /* OID of the access method's cost fcn */
Oid indproc; /* if a functional index */ Oid indproc; /* OID of func if functional index, else 0 */
List *indpred; /* if a partial index */ List *indpred; /* predicate if a partial index, else NIL */
bool unique; /* if a unique index */ 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; } IndexOptInfo;
...@@ -354,18 +351,9 @@ typedef struct Path ...@@ -354,18 +351,9 @@ typedef struct Path
* NoMovementScanDirection for an indexscan, but the planner wants to * NoMovementScanDirection for an indexscan, but the planner wants to
* distinguish ordered from unordered indexes for building pathkeys.) * 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 * '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 * 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 * coming from join clauses make the scan more selective than the parent
* rel's restrict clauses alone would do. * rel's restrict clauses alone would do.
*---------- *----------
...@@ -376,8 +364,6 @@ typedef struct IndexPath ...@@ -376,8 +364,6 @@ typedef struct IndexPath
List *indexinfo; List *indexinfo;
List *indexqual; List *indexqual;
ScanDirection indexscandir; ScanDirection indexscandir;
Relids joinrelids; /* other rels mentioned in indexqual */
bool alljoinquals; /* all indexquals derived from JOIN conds? */
double rows; /* estimated number of result tuples */ double rows; /* estimated number of result tuples */
} IndexPath; } IndexPath;
...@@ -616,4 +602,42 @@ typedef struct JoinInfo ...@@ -616,4 +602,42 @@ typedef struct JoinInfo
List *jinfo_restrictinfo; /* relevant RestrictInfos */ List *jinfo_restrictinfo; /* relevant RestrictInfos */
} JoinInfo; } 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 */ #endif /* RELATION_H */
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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); ...@@ -40,6 +40,8 @@ extern void debug_print_rel(Query *root, RelOptInfo *rel);
* routines to generate index paths * routines to generate index paths
*/ */
extern void create_index_paths(Query *root, RelOptInfo *rel); 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, extern Oid indexable_operator(Expr *clause, Oid opclass,
bool indexkey_on_left); bool indexkey_on_left);
extern List *extract_or_indexqual_conditions(RelOptInfo *rel, extern List *extract_or_indexqual_conditions(RelOptInfo *rel,
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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); ...@@ -20,5 +20,8 @@ extern bool restriction_is_or_clause(RestrictInfo *restrictinfo);
extern List *get_actual_clauses(List *restrictinfo_list); extern List *get_actual_clauses(List *restrictinfo_list);
extern void get_actual_join_clauses(List *restrictinfo_list, extern void get_actual_join_clauses(List *restrictinfo_list,
List **joinquals, List **otherquals); List **joinquals, List **otherquals);
extern List *remove_redundant_join_clauses(Query *root,
List *restrictinfo_list,
JoinType jointype);
#endif /* RESTRICTINFO_H */ #endif /* RESTRICTINFO_H */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment