diff options
Diffstat (limited to 'src/backend/optimizer/path/indxpath.c')
-rw-r--r-- | src/backend/optimizer/path/indxpath.c | 1206 |
1 files changed, 1206 insertions, 0 deletions
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c new file mode 100644 index 00000000000..844571847f9 --- /dev/null +++ b/src/backend/optimizer/path/indxpath.c @@ -0,0 +1,1206 @@ +/*------------------------------------------------------------------------- + * + * indxpath.c-- + * Routines to determine which indices are usable for scanning a + * given relation + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.1.1.1 1996/07/09 06:21:35 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <math.h> +#include "postgres.h" +#include "access/attnum.h" +#include "access/heapam.h" +#include "access/nbtree.h" + +#include "nodes/pg_list.h" +#include "nodes/relation.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" + +#include "utils/lsyscache.h" +#include "utils/elog.h" + +#include "optimizer/internal.h" +#include "optimizer/paths.h" +#include "optimizer/clauses.h" +#include "optimizer/clauseinfo.h" +#include "optimizer/plancat.h" +#include "optimizer/keys.h" +#include "optimizer/cost.h" +#include "optimizer/pathnode.h" +#include "optimizer/xfunc.h" +#include "optimizer/ordering.h" + + +#include "catalog/catname.h" +#include "catalog/pg_amop.h" +#include "catalog/pg_proc.h" + +#include "executor/executor.h" +#include "parser/parsetree.h" /* for getrelid() */ + + +static void match_index_orclauses(Rel *rel, Rel *index, int indexkey, + int xclass, List *clauseinfo_list); +static bool match_index_to_operand(int indexkey, Expr *operand, + Rel *rel, Rel *index); +static List *match_index_orclause(Rel *rel, Rel *index, int indexkey, + int xclass, List *or_clauses, List *other_matching_indices); +static List *group_clauses_by_indexkey(Rel *rel, Rel *index, + int *indexkeys, Oid *classes, List *clauseinfo_list, + bool join); +static CInfo *match_clause_to_indexkey(Rel *rel, Rel *index, int indexkey, + int xclass, CInfo *clauseInfo, bool join); +static bool pred_test(List *predicate_list, List *clauseinfo_list, + List *joininfo_list); +static bool one_pred_test(Expr *predicate, List *clauseinfo_list); +static bool one_pred_clause_expr_test(Expr *predicate, Node *clause); +static bool one_pred_clause_test(Expr *predicate, Node *clause); +static bool clause_pred_clause_test(Expr *predicate, Node *clause); +static List *indexable_joinclauses(Rel *rel, Rel *index, List *joininfo_list); +static List *index_innerjoin(Query *root, Rel *rel, + List *clausegroup_list, Rel *index); +static List *create_index_paths(Query *root, Rel *rel, Rel *index, + List *clausegroup_list, bool join); +static List *add_index_paths(List *indexpaths, List *new_indexpaths); +static bool function_index_operand(Expr *funcOpnd, Rel *rel, Rel *index); +static bool SingleAttributeIndex(Rel *index); + +/* If Spyros can use a constant PRS2_BOOL_TYPEID, I can use this */ +#define BOOL_TYPEID ((Oid) 16) + +/* + * find-index-paths-- + * Finds all possible index paths by determining which indices in the + * list 'indices' are usable. + * + * To be usable, an index must match against either a set of + * restriction clauses or join clauses. + * + * Note that the current implementation requires that there exist + * matching clauses for every key in the index (i.e., no partial + * matches are allowed). + * + * If an index can't be used with restriction clauses, but its keys + * match those of the result sort order (according to information stored + * within 'sortkeys'), then the index is also considered. + * + * 'rel' is the relation entry to which these index paths correspond + * 'indices' is a list of possible index paths + * 'clauseinfo-list' is a list of restriction clauseinfo nodes for 'rel' + * 'joininfo-list' is a list of joininfo nodes for 'rel' + * 'sortkeys' is a node describing the result sort order (from + * (find_sortkeys)) + * + * Returns a list of index nodes. + * + */ +List * +find_index_paths (Query *root, + Rel *rel, + List *indices, + List *clauseinfo_list, + List *joininfo_list) +{ + List *scanclausegroups = NIL; + List *scanpaths = NIL; + Rel *index = (Rel *)NULL; + List *joinclausegroups = NIL; + List *joinpaths = NIL; + List *retval = NIL; + extern List *add_index_paths(); + + if(indices == NIL) + return(NULL); + + index = (Rel*)lfirst (indices); + + retval = find_index_paths(root, + rel, + lnext (indices), + clauseinfo_list, + joininfo_list); + + /* If this is a partial index, return if it fails the predicate test */ + if (index->indpred != NIL) + if (!pred_test(index->indpred, clauseinfo_list, joininfo_list)) + return retval; + + /* 1. If this index has only one key, try matching it against + * subclauses of an 'or' clause. The fields of the clauseinfo + * nodes are marked with lists of the matching indices no path + * are actually created. + * + * XXX NOTE: Currently btrees dos not support indices with + * > 1 key, so the following test will always be true for + * now but we have decided not to support index-scans + * on disjunction . -- lp + */ + if (SingleAttributeIndex(index)) + { + match_index_orclauses (rel, + index, + index->indexkeys[0], + index->classlist[0], + clauseinfo_list); + } + + /* + * 2. If the keys of this index match any of the available + * restriction clauses, then create pathnodes corresponding + * to each group of usable clauses. + */ + scanclausegroups = group_clauses_by_indexkey(rel, + index, + index->indexkeys, + index->classlist, + clauseinfo_list, + false); + + scanpaths = NIL; + if (scanclausegroups != NIL) + scanpaths = create_index_paths (root, + rel, + index, + scanclausegroups, + false); + + /* + * 3. If this index can be used with any join clause, then + * create pathnodes for each group of usable clauses. An + * index can be used with a join clause if its ordering is + * useful for a mergejoin, or if the index can possibly be + * used for scanning the inner relation of a nestloop join. + */ + joinclausegroups = indexable_joinclauses(rel,index,joininfo_list); + joinpaths = NIL; + + if (joinclausegroups != NIL) + { + List *new_join_paths = create_index_paths(root, rel, + index, + joinclausegroups, + true); + List *innerjoin_paths = index_innerjoin(root, rel,joinclausegroups,index); + + rel->innerjoin = nconc (rel->innerjoin, innerjoin_paths); + joinpaths = new_join_paths; + } + + /* + * Some sanity checks to make sure that + * the indexpath is valid. + */ + if (joinpaths!=NULL) + retval = add_index_paths(joinpaths,retval); + if (scanpaths!=NULL) + retval = add_index_paths(scanpaths,retval); + + return retval; + +} + + +/**************************************************************************** + * ---- ROUTINES TO MATCH 'OR' CLAUSES ---- + ****************************************************************************/ + + +/* + * match-index-orclauses-- + * Attempt to match an index against subclauses within 'or' clauses. + * If the index does match, then the clause is marked with information + * about the index. + * + * Essentially, this adds 'index' to the list of indices in the + * ClauseInfo field of each of the clauses which it matches. + * + * 'rel' is the node of the relation on which the index is defined. + * 'index' is the index node. + * 'indexkey' is the (single) key of the index + * 'class' is the class of the operator corresponding to 'indexkey'. + * 'clauseinfo-list' is the list of available restriction clauses. + * + * Returns nothing. + * + */ +static void +match_index_orclauses(Rel *rel, + Rel *index, + int indexkey, + int xclass, + List *clauseinfo_list) +{ + CInfo *clauseinfo = (CInfo*)NULL; + List *i = NIL; + + foreach (i, clauseinfo_list) { + clauseinfo = (CInfo*)lfirst(i); + if (valid_or_clause(clauseinfo)) { + + /* Mark the 'or' clause with a list of indices which + * match each of its subclauses. The list is + * generated by adding 'index' to the existing + * list where appropriate. + */ + clauseinfo->indexids = + match_index_orclause (rel,index,indexkey, + xclass, + clauseinfo->clause->args, + clauseinfo->indexids); + } + } +} + +/* + * match_index_operand-- + * Generalize test for a match between an existing index's key + * and the operand on the rhs of a restriction clause. Now check + * for functional indices as well. + */ +static bool +match_index_to_operand(int indexkey, + Expr *operand, + Rel *rel, + Rel *index) +{ + /* + * Normal index. + */ + if (index->indproc == InvalidOid) + return match_indexkey_operand(indexkey, (Var*)operand, rel); + + /* + * functional index check + */ + return (function_index_operand(operand, rel, index)); +} + +/* + * match-index-orclause-- + * Attempts to match an index against the subclauses of an 'or' clause. + * + * A match means that: + * (1) the operator within the subclause can be used with one + * of the index's operator classes, and + * (2) there is a usable key that matches the variable within a + * sargable clause. + * + * 'or-clauses' are the remaining subclauses within the 'or' clause + * 'other-matching-indices' is the list of information on other indices + * that have already been matched to subclauses within this + * particular 'or' clause (i.e., a list previously generated by + * this routine) + * + * Returns a list of the form ((a b c) (d e f) nil (g h) ...) where + * a,b,c are nodes of indices that match the first subclause in + * 'or-clauses', d,e,f match the second subclause, no indices + * match the third, g,h match the fourth, etc. + */ +static List * +match_index_orclause(Rel *rel, + Rel *index, + int indexkey, + int xclass, + List *or_clauses, + List *other_matching_indices) +{ + Node *clause = NULL; + List *matched_indices = other_matching_indices; + List *index_list = NIL; + List *clist; + List *ind; + + if (!matched_indices) + matched_indices = lcons(NIL, NIL); + + for (clist = or_clauses, ind = matched_indices; + clist; + clist = lnext(clist), ind = lnext(ind)) + { + clause = lfirst(clist); + if (is_opclause (clause) && + op_class(((Oper*)((Expr*)clause)->oper)->opno, + xclass, index->relam) && + match_index_to_operand(indexkey, + (Expr*)get_leftop((Expr*)clause), + rel, + index) && + IsA(get_rightop((Expr*)clause),Const)) { + + matched_indices = lcons(index, matched_indices); + index_list = lappend(index_list, + matched_indices); + } + } + return(index_list); + +} + +/**************************************************************************** + * ---- ROUTINES TO CHECK RESTRICTIONS ---- + ****************************************************************************/ + + +/* + * 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 the there can + * be only 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) \ + (indexkeys[0] == 0 || \ + (index->indproc != InvalidOid)) + +/* + * group-clauses-by-indexkey-- + * Determines whether there are clauses which will match each and every + * one of the remaining keys of an index. + * + * 'rel' is the node of the relation corresponding to the index. + * 'indexkeys' are the remaining index keys to be matched. + * 'classes' are the classes of the index operators on those keys. + * 'clauses' is either: + * (1) the list of available restriction clauses on a single + * relation, or + * (2) a list of join clauses between 'rel' and a fixed set of + * relations, + * depending on the value of 'join'. + * 'startlist' is a list of those clause nodes that have matched the keys + * that have already been checked. + * 'join' is a flag indicating that the clauses being checked are join + * clauses. + * + * Returns all possible groups of clauses that will match (given that + * one or more clauses can match any of the remaining keys). + * E.g., if you have clauses A, B, and C, ((A B) (A C)) might be + * returned for an index with 2 keys. + * + */ +static List * +group_clauses_by_indexkey(Rel *rel, + Rel *index, + int *indexkeys, + Oid *classes, + List *clauseinfo_list, + bool join) +{ + List *curCinfo = NIL; + CInfo *matched_clause = (CInfo*)NULL; + List *clausegroup = NIL; + + + if (clauseinfo_list == NIL) + return NIL; + + foreach (curCinfo,clauseinfo_list) { + CInfo *temp = (CInfo*)lfirst(curCinfo); + int *curIndxKey = indexkeys; + Oid *curClass = classes; + + do { + /* + * If we can't find any matching clauses for the first of + * the remaining keys, give up. + */ + matched_clause = match_clause_to_indexkey (rel, + index, + curIndxKey[0], + curClass[0], + temp, + join); + if (!matched_clause) + break; + + clausegroup = lcons(matched_clause, clausegroup); + curIndxKey++; + curClass++; + + } while ( !DoneMatchingIndexKeys(curIndxKey, index) ); + } + + if (clausegroup != NIL) + return(lcons(clausegroup, NIL)); + return NIL; +} + +/* + * IndexScanableClause () MACRO + * + * Generalize condition on which we match a clause with an index. + * Now we can match with functional indices. + */ +#define IndexScanableOperand(opnd, indkeys, rel, index) \ + ((index->indproc == InvalidOid) ? \ + equal_indexkey_var(indkeys,opnd) : \ + function_index_operand((Expr*)opnd,rel,index)) + +/* + * match_clause_to-indexkey-- + * Finds the first of a relation's available restriction clauses that + * matches a key of an index. + * + * To match, the clause must: + * (1) be in the form (op var const) if the clause is a single- + * relation clause, and + * (2) contain an operator which is in the same class as the index + * operator for this key. + * + * If the clause being matched is a join clause, then 'join' is t. + * + * Returns a single clauseinfo node corresponding to the matching + * clause. + * + * NOTE: returns nil if clause is an or_clause. + * + */ +static CInfo * +match_clause_to_indexkey(Rel *rel, + Rel *index, + int indexkey, + int xclass, + CInfo *clauseInfo, + bool join) +{ + Expr *clause = clauseInfo->clause; + Var *leftop, *rightop; + Oid join_op = InvalidOid; + bool isIndexable = false; + + if (or_clause((Node*)clause) || + not_clause((Node*)clause) || single_node((Node*)clause)) + return ((CInfo*)NULL); + + leftop = get_leftop(clause); + rightop = get_rightop(clause); + /* + * If this is not a join clause, check for clauses of the form: + * (operator var/func constant) and (operator constant var/func) + */ + if (!join) + { + Oid restrict_op = InvalidOid; + + /* + * Check for standard s-argable clause + */ + if (IsA(rightop,Const)) + { + restrict_op = ((Oper*)((Expr*)clause)->oper)->opno; + isIndexable = + ( op_class(restrict_op, xclass, index->relam) && + IndexScanableOperand(leftop, + indexkey, + rel, + index) ); + } + + /* + * Must try to commute the clause to standard s-arg format. + */ + else if (IsA(leftop,Const)) + { + restrict_op = + get_commutator(((Oper*)((Expr*)clause)->oper)->opno); + + if ( (restrict_op != InvalidOid) && + op_class(restrict_op, xclass, index->relam) && + IndexScanableOperand(rightop, + indexkey,rel,index) ) + { + isIndexable = true; + /* + * In place list modification. + * (op const var/func) -> (op var/func const) + */ + /* BUG! Old version: + CommuteClause(clause, restrict_op); + */ + CommuteClause((Node*)clause); + } + } + } + /* + * Check for an indexable scan on one of the join relations. + * clause is of the form (operator var/func var/func) + */ + else + { + if (match_index_to_operand(indexkey,(Expr*)rightop,rel,index)) { + + join_op = get_commutator(((Oper*)((Expr*)clause)->oper)->opno); + + } else if (match_index_to_operand(indexkey, + (Expr*)leftop,rel,index)) { + join_op = ((Oper*)((Expr*)clause)->oper)->opno; + } + + if ( join_op && op_class(join_op,xclass,index->relam) && + join_clause_p((Node*)clause)) + { + isIndexable = true; + + /* + * If we're using the operand's commutator we must + * commute the clause. + */ + if (join_op != ((Oper*)((Expr*)clause)->oper)->opno) + CommuteClause((Node*)clause); + } + } + + if (isIndexable) + return(clauseInfo); + + return(NULL); +} + +/**************************************************************************** + * ---- ROUTINES TO DO PARTIAL INDEX PREDICATE TESTS ---- + ****************************************************************************/ + +/* + * pred_test-- + * Does the "predicate inclusion test" for partial indexes. + * + * Recursively checks whether the clauses in clauseinfo_list imply + * that the given predicate is true. + * + * This routine (together with the routines it calls) iterates over + * ANDs in the predicate first, then reduces the qualification + * clauses down to their constituent terms, and iterates over ORs + * in the predicate last. This order is important to make the test + * succeed whenever possible (assuming the predicate has been + * successfully cnfify()-ed). --Nels, Jan '93 + */ +static bool +pred_test(List *predicate_list, List *clauseinfo_list, List *joininfo_list) +{ + List *pred, *items, *item; + + /* + * Note: if Postgres tried to optimize queries by forming equivalence + * classes over equi-joined attributes (i.e., if it recognized that a + * qualification such as "where a.b=c.d and a.b=5" could make use of + * an index on c.d), then we could use that equivalence class info + * here with joininfo_list to do more complete tests for the usability + * of a partial index. For now, the test only uses restriction + * clauses (those in clauseinfo_list). --Nels, Dec '92 + */ + + if (predicate_list == NULL) + return true; /* no predicate: the index is usable */ + if (clauseinfo_list == NULL) + return false; /* no restriction clauses: the test must fail */ + + foreach (pred, predicate_list) { + /* if any clause is not implied, the whole predicate is not implied */ + if (and_clause(lfirst(pred))) { + items = ((Expr*)lfirst(pred))->args; + foreach (item, items) { + if (!one_pred_test(lfirst(item), clauseinfo_list)) + return false; + } + } + else if (!one_pred_test(lfirst(pred), clauseinfo_list)) + return false; + } + return true; +} + + +/* + * one_pred_test-- + * Does the "predicate inclusion test" for one conjunct of a predicate + * expression. + */ +static bool +one_pred_test(Expr *predicate, List *clauseinfo_list) +{ + CInfo *clauseinfo; + List *item; + + Assert(predicate != NULL); + foreach (item, clauseinfo_list) { + clauseinfo = (CInfo *)lfirst(item); + /* if any clause implies the predicate, return true */ + if (one_pred_clause_expr_test(predicate, (Node*)clauseinfo->clause)) + return true; + } + return false; +} + + +/* + * one_pred_clause_expr_test-- + * Does the "predicate inclusion test" for a general restriction-clause + * expression. + */ +static bool +one_pred_clause_expr_test(Expr *predicate, Node *clause) +{ + List *items, *item; + + if (is_opclause(clause)) + return one_pred_clause_test(predicate, clause); + else if (or_clause(clause)) { + items = ((Expr*)clause)->args; + foreach (item, items) { + /* if any OR item doesn't imply the predicate, clause doesn't */ + if (!one_pred_clause_expr_test(predicate, lfirst(item))) + return false; + } + return true; + }else if (and_clause(clause)) { + items = ((Expr*)clause)->args; + foreach (item, items) { + /* if any AND item implies the predicate, the whole clause does */ + if (one_pred_clause_expr_test(predicate, lfirst(item))) + return true; + } + return false; + }else { + /* unknown clause type never implies the predicate */ + return false; + } +} + + +/* + * one_pred_clause_test-- + * Does the "predicate inclusion test" for one conjunct of a predicate + * expression for a simple restriction clause. + */ +static bool +one_pred_clause_test(Expr *predicate, Node *clause) +{ + List *items, *item; + + if (is_opclause((Node*)predicate)) + return clause_pred_clause_test(predicate, clause); + else if (or_clause((Node*)predicate)) { + items = predicate->args; + foreach (item, items) { + /* if any item is implied, the whole predicate is implied */ + if (one_pred_clause_test(lfirst(item), clause)) + return true; + } + return false; + }else if (and_clause((Node*)predicate)) { + items = predicate->args; + foreach (item, items) { + /* + * if any item is not implied, the whole predicate is not + * implied + */ + if (!one_pred_clause_test(lfirst(item), clause)) + return false; + } + return true; + } + else { + elog(DEBUG, "Unsupported predicate type, index will not be used"); + return false; + } +} + + +/* + * Define an "operator implication table" for btree operators ("strategies"). + * The "strategy numbers" are: (1) < (2) <= (3) = (4) >= (5) > + * + * The interpretation of: + * + * test_op = BT_implic_table[given_op-1][target_op-1] + * + * where test_op, given_op and target_op are strategy numbers (from 1 to 5) + * of btree operators, is as follows: + * + * If you know, for some ATTR, that "ATTR given_op CONST1" is true, and you + * want to determine whether "ATTR target_op CONST2" must also be true, then + * you can use "CONST1 test_op CONST2" as a test. If this test returns true, + * then the target expression must be true; if the test returns false, then + * the target expression may be false. + * + * An entry where test_op==0 means the implication cannot be determined, i.e., + * this test should always be considered false. + */ + +StrategyNumber BT_implic_table[BTMaxStrategyNumber][BTMaxStrategyNumber] = { + {2, 2, 0, 0, 0}, + {1, 2, 0, 0, 0}, + {1, 2, 3, 4, 5}, + {0, 0, 0, 4, 5}, + {0, 0, 0, 4, 4} +}; + + +/* + * clause_pred_clause_test-- + * Use operator class info to check whether clause implies predicate. + * + * Does the "predicate inclusion test" for a "simple clause" predicate + * for a single "simple clause" restriction. Currently, this only handles + * (binary boolean) operators that are in some btree operator class. + * Eventually, rtree operators could also be handled by defining an + * appropriate "RT_implic_table" array. + */ +static bool +clause_pred_clause_test(Expr *predicate, Node *clause) +{ + Var *pred_var, *clause_var; + Const *pred_const, *clause_const; + Oid pred_op, clause_op, test_op; + Oid opclass_id; + StrategyNumber pred_strategy, clause_strategy, test_strategy; + Oper *test_oper; + Expr *test_expr; + bool test_result, isNull; + Relation relation; + HeapScanDesc scan; + HeapTuple tuple; + ScanKeyData entry[3]; + Form_pg_amop form; + + pred_var = (Var*)get_leftop(predicate); + pred_const = (Const*)get_rightop(predicate); + clause_var = (Var*)get_leftop((Expr*)clause); + clause_const = (Const*)get_rightop((Expr*)clause); + + /* Check the basic form; for now, only allow the simplest case */ + if (!is_opclause(clause) || + !IsA(clause_var,Var) || + !IsA(clause_const,Const) || + !IsA(predicate->oper,Oper) || + !IsA(pred_var,Var) || + !IsA(pred_const,Const)) { + return false; + } + + /* + * The implication can't be determined unless the predicate and the clause + * refer to the same attribute. + */ + if (clause_var->varattno != pred_var->varattno) + return false; + + /* Get the operators for the two clauses we're comparing */ + pred_op = ((Oper*)((Expr*)predicate)->oper)->opno; + clause_op = ((Oper*)((Expr*)clause)->oper)->opno; + + + /* + * 1. Find a "btree" strategy number for the pred_op + */ + /* XXX - hardcoded amopid value 403 to find "btree" operator classes */ + ScanKeyEntryInitialize(&entry[0], 0, + Anum_pg_amop_amopid, + ObjectIdEqualRegProcedure, + ObjectIdGetDatum(403)); + + ScanKeyEntryInitialize(&entry[1], 0, + Anum_pg_amop_amopopr, + ObjectIdEqualRegProcedure, + ObjectIdGetDatum(pred_op)); + + relation = heap_openr(AccessMethodOperatorRelationName); + + /* + * The following assumes that any given operator will only be in a single + * btree operator class. This is true at least for all the pre-defined + * operator classes. If it isn't true, then whichever operator class + * happens to be returned first for the given operator will be used to + * find the associated strategy numbers for the test. --Nels, Jan '93 + */ + scan = heap_beginscan(relation, false, NowTimeQual, 2, entry); + tuple = heap_getnext(scan, false, (Buffer *)NULL); + if (! HeapTupleIsValid(tuple)) { + elog(DEBUG, "clause_pred_clause_test: unknown pred_op"); + return false; + } + form = (Form_pg_amop) GETSTRUCT(tuple); + + /* Get the predicate operator's strategy number (1 to 5) */ + pred_strategy = (StrategyNumber)form->amopstrategy; + + /* Remember which operator class this strategy number came from */ + opclass_id = form->amopclaid; + + heap_endscan(scan); + + + /* + * 2. From the same opclass, find a strategy num for the clause_op + */ + ScanKeyEntryInitialize(&entry[1], 0, + Anum_pg_amop_amopclaid, + ObjectIdEqualRegProcedure, + ObjectIdGetDatum(opclass_id)); + + ScanKeyEntryInitialize(&entry[2], 0, + Anum_pg_amop_amopopr, + ObjectIdEqualRegProcedure, + ObjectIdGetDatum(clause_op)); + + scan = heap_beginscan(relation, false, NowTimeQual, 3, entry); + tuple = heap_getnext(scan, false, (Buffer *)NULL); + if (! HeapTupleIsValid(tuple)) { + elog(DEBUG, "clause_pred_clause_test: unknown clause_op"); + return false; + } + form = (Form_pg_amop) GETSTRUCT(tuple); + + /* Get the restriction clause operator's strategy number (1 to 5) */ + clause_strategy = (StrategyNumber)form->amopstrategy; + heap_endscan(scan); + + + /* + * 3. Look up the "test" strategy number in the implication table + */ + + test_strategy = BT_implic_table[clause_strategy-1][pred_strategy-1]; + if (test_strategy == 0) + return false; /* the implication cannot be determined */ + + + /* + * 4. From the same opclass, find the operator for the test strategy + */ + + ScanKeyEntryInitialize(&entry[2], 0, + Anum_pg_amop_amopstrategy, + Integer16EqualRegProcedure, + Int16GetDatum(test_strategy)); + + scan = heap_beginscan(relation, false, NowTimeQual, 3, entry); + tuple = heap_getnext(scan, false, (Buffer *)NULL); + if (! HeapTupleIsValid(tuple)) { + elog(DEBUG, "clause_pred_clause_test: unknown test_op"); + return false; + } + form = (Form_pg_amop) GETSTRUCT(tuple); + + /* Get the test operator */ + test_op = form->amopopr; + heap_endscan(scan); + + + /* + * 5. Evaluate the test + */ + test_oper = makeOper(test_op, /* opno */ + InvalidOid, /* opid */ + BOOL_TYPEID, /* opresulttype */ + 0, /* opsize */ + NULL); /* op_fcache */ + (void) replace_opid(test_oper); + + test_expr = make_opclause(test_oper, + copyObject(clause_const), + copyObject(pred_const)); + +#ifndef OMIT_PARTIAL_INDEX + test_result = ExecEvalExpr((Node*)test_expr, NULL, &isNull, NULL); +#endif /* OMIT_PARTIAL_INDEX */ + if (isNull) { + elog(DEBUG, "clause_pred_clause_test: null test result"); + return false; + } + return test_result; +} + + +/**************************************************************************** + * ---- ROUTINES TO CHECK JOIN CLAUSES ---- + ****************************************************************************/ + +/* + * indexable-joinclauses-- + * Finds all groups of join clauses from among 'joininfo-list' that can + * be used in conjunction with 'index'. + * + * The first clause in the group is marked as having the other relation + * in the join clause as its outer join relation. + * + * Returns a list of these clause groups. + * + */ +static List * +indexable_joinclauses(Rel *rel, Rel *index, List *joininfo_list) +{ + JInfo *joininfo = (JInfo*)NULL; + List *cg_list = NIL; + List *i = NIL; + List *clausegroups = NIL; + + foreach(i,joininfo_list) { + joininfo = (JInfo*)lfirst(i); + clausegroups = + group_clauses_by_indexkey (rel, + index, + index->indexkeys, + index->classlist, + joininfo->jinfoclauseinfo, + true); + + if (clausegroups != NIL) { + List *clauses = lfirst(clausegroups); + + ((CInfo*)lfirst(clauses))->cinfojoinid = + joininfo->otherrels; + } + cg_list = nconc(cg_list,clausegroups); + } + return(cg_list); +} + +/**************************************************************************** + * ---- PATH CREATION UTILITIES ---- + ****************************************************************************/ + +/* + * extract_restrict_clauses - + * the list of clause info contains join clauses and restriction clauses. + * This routine returns the restriction clauses only. + */ +static List * +extract_restrict_clauses(List *clausegroup) +{ + List *restrict_cls = NIL; + List *l; + + foreach (l, clausegroup) { + CInfo *cinfo = lfirst(l); + + if (!join_clause_p((Node*)cinfo->clause)) { + restrict_cls = lappend(restrict_cls, cinfo); + } + } + return restrict_cls; +} + +/* + * index-innerjoin-- + * Creates index path nodes corresponding to paths to be used as inner + * relations in nestloop joins. + * + * 'clausegroup-list' is a list of list of clauseinfo nodes which can use + * 'index' on their inner relation. + * + * Returns a list of index pathnodes. + * + */ +static List * +index_innerjoin(Query *root, Rel *rel, List *clausegroup_list, Rel *index) +{ + List *clausegroup = NIL; + List *cg_list = NIL; + List *i = NIL; + IndexPath *pathnode = (IndexPath*)NULL; + Cost temp_selec; + float temp_pages; + + foreach(i,clausegroup_list) { + List *attnos, *values, *flags; + + clausegroup = lfirst(i); + pathnode = makeNode(IndexPath); + + get_joinvars(lfirsti(rel->relids),clausegroup, + &attnos, &values, &flags); + index_selectivity(lfirsti(index->relids), + index->classlist, + get_opnos(clausegroup), + getrelid((int)lfirst(rel->relids), + root->rtable), + attnos, + values, + flags, + length(clausegroup), + &temp_pages, + &temp_selec); + pathnode->path.pathtype = T_IndexScan; + pathnode->path.parent = rel; + pathnode->indexid = index->relids; + pathnode->indexqual = clausegroup; + + pathnode->path.joinid = ((CInfo*)lfirst(clausegroup))->cinfojoinid; + + pathnode->path.path_cost = + cost_index((Oid)lfirst(index->relids), + (int)temp_pages, + temp_selec, + rel->pages, + rel->tuples, + index->pages, + index->tuples, + true); + + /* copy clauseinfo list into path for expensive function processing + -- JMH, 7/7/92 */ + pathnode->path.locclauseinfo = + set_difference(copyObject((Node*)rel->clauseinfo), + clausegroup); + +#if 0 /* fix xfunc */ + /* add in cost for expensive functions! -- JMH, 7/7/92 */ + if (XfuncMode != XFUNC_OFF) { + ((Path*)pathnode)->path_cost += + xfunc_get_path_cost((Path*)pathnode); + } +#endif + cg_list = lappend(cg_list,pathnode); + } + return(cg_list); +} + +/* + * create-index-paths-- + * Creates a list of index path nodes for each group of clauses + * (restriction or join) that can be used in conjunction with an index. + * + * 'rel' is the relation for which 'index' is defined + * 'clausegroup-list' is the list of clause groups (lists of clauseinfo + * nodes) grouped by mergesortorder + * 'join' is a flag indicating whether or not the clauses are join + * clauses + * + * Returns a list of new index path nodes. + * + */ +static List * +create_index_paths(Query *root, + Rel *rel, + Rel *index, + List *clausegroup_list, + bool join) +{ + List *clausegroup = NIL; + List *ip_list = NIL; + List *i = NIL; + List *j = NIL; + IndexPath *temp_path; + + foreach(i, clausegroup_list) { + CInfo *clauseinfo; + List *temp_node = NIL; + bool temp = true; + + clausegroup = lfirst(i); + + foreach (j,clausegroup) { + clauseinfo = (CInfo*)lfirst(j); + if (!(join_clause_p((Node*)clauseinfo->clause) && + equal_path_merge_ordering(index->ordering, + clauseinfo->mergesortorder))) { + temp = false; + } + } + + if (!join || temp) { /* restriction, ordering scan */ + temp_path = create_index_path (root, rel,index,clausegroup,join); + temp_node = + lcons(temp_path, NIL); + ip_list = nconc(ip_list,temp_node); + } + } + return(ip_list); +} + +static List * +add_index_paths(List *indexpaths, List *new_indexpaths) +{ + return append(indexpaths, new_indexpaths); +} + +static bool +function_index_operand(Expr *funcOpnd, Rel *rel, Rel *index) +{ + Oid heapRelid = (Oid)lfirst(rel->relids); + Func *function; + List *funcargs; + int *indexKeys = index->indexkeys; + List *arg; + int i; + + /* + * sanity check, make sure we know what we're dealing with here. + */ + if (funcOpnd==NULL || + nodeTag(funcOpnd)!=T_Expr || funcOpnd->opType!=FUNC_EXPR || + funcOpnd->oper==NULL || indexKeys==NULL) + return false; + + function = (Func*)funcOpnd->oper; + funcargs = funcOpnd->args; + + if (function->funcid != index->indproc) + return false; + + /* + * Check that the arguments correspond to the same arguments used + * to create the functional index. To do this we must check that + * 1. refer to the right relatiion. + * 2. the args have the right attr. numbers in the right order. + * + * + * Check all args refer to the correct relation (i.e. the one with + * the functional index defined on it (rel). To do this we can + * simply compare range table entry numbers, they must be the same. + */ + foreach (arg, funcargs) { + if (heapRelid != ((Var*)lfirst(arg))->varno) + return false; + } + + /* + * check attr numbers and order. + */ + i = 0; + foreach (arg, funcargs) { + + if (indexKeys[i]==0) + return (false); + + if (((Var*)lfirst(arg))->varattno != indexKeys[i]) + return (false); + + i++; + } + + return true; +} + +static bool +SingleAttributeIndex(Rel *index) +{ + /* + * return false for now as I don't know if we support index scans + * on disjunction and the code doesn't work + */ + return (false); + +#if 0 + /* + * Non-functional indices. + */ + if (index->indproc == InvalidOid) + return (index->indexkeys[0] != 0 && + index->indexkeys[1] == 0); + + /* + * We have a functional index which is a single attr index + */ + return true; +#endif +} |