Commit 24679346 authored by Tom Lane's avatar Tom Lane

Modify partial-index-predicate applicability tester to test whether

clauses are equal(), before trying to match them up using btree opclass
inference rules.  This allows it to recognize many simple cases involving
non-btree operations, for example 'x IS NULL'.  Clean up code a little.
parent 7d6fbe15
<!-- <!--
$Header: /cvsroot/pgsql/doc/src/sgml/ref/create_index.sgml,v 1.20 2001/07/16 05:06:57 tgl Exp $ $Header: /cvsroot/pgsql/doc/src/sgml/ref/create_index.sgml,v 1.21 2001/08/06 18:09:45 tgl Exp $
Postgres documentation Postgres documentation
--> -->
...@@ -256,21 +256,26 @@ ERROR: Cannot create index: 'index_name' already exists. ...@@ -256,21 +256,26 @@ ERROR: Cannot create index: 'index_name' already exists.
billed and unbilled orders where the unbilled orders take up a small billed and unbilled orders where the unbilled orders take up a small
fraction of the total table and yet that is an often used section, you fraction of the total table and yet that is an often used section, you
can improve performance by creating an index on just that portion. can improve performance by creating an index on just that portion.
Another possible application is to use <command>WHERE</command> with
<command>UNIQUE</command> to enforce uniqueness over a subset of a
table.
</para> </para>
<para> <para>
The expression used in the <command>WHERE</command> clause may refer The expression used in the <command>WHERE</command> clause may refer
only to columns of the underlying table (but it can use all columns, only to columns of the underlying table (but it can use all columns,
not only the one(s) being indexed). Currently, the not only the one(s) being indexed). Presently, sub-SELECTs and
<productname>PostgreSQL</productname> planner can only devise query aggregate expressions are also forbidden in <command>WHERE</command>.
plans that make use of a partial index when the predicate is built from </para>
<command>AND</command> and <command>OR</command> combinations of
elements of the form <para>
<firstterm>column</firstterm> All functions and operators used in an index definition must be
<firstterm>operator</firstterm> <firstterm>cachable</>, that is, their results must depend only on
<firstterm>constant</firstterm>. their input arguments and never on any outside influence (such as
However, more general predicates may still be useful in conjunction the contents of another table or the current time). This restriction
with UNIQUE indexes, to enforce uniqueness over a subset of a table. ensures that the behavior of the index is well-defined. To use a
user-defined function in an index, remember to mark the function cachable
when you create it.
</para> </para>
<para> <para>
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/indexcmds.c,v 1.53 2001/07/17 21:53:01 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/commands/indexcmds.c,v 1.54 2001/08/06 18:09:45 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -146,10 +146,12 @@ DefineIndex(char *heapRelationName, ...@@ -146,10 +146,12 @@ DefineIndex(char *heapRelationName,
/* /*
* Convert the partial-index predicate from parsetree form to * Convert the partial-index predicate from parsetree form to
* an implicit-AND qual expression, for easier evaluation at runtime. * an implicit-AND qual expression, for easier evaluation at runtime.
* While we are at it, we reduce it to a canonical (CNF or DNF) form
* to simplify the task of proving implications.
*/ */
if (predicate != NULL && rangetable != NIL) if (predicate != NULL && rangetable != NIL)
{ {
cnfPred = cnfify((Expr *) copyObject(predicate), true); cnfPred = canonicalize_qual((Expr *) copyObject(predicate), true);
fix_opids((Node *) cnfPred); fix_opids((Node *) cnfPred);
CheckPredicate(cnfPred, rangetable, relationId); CheckPredicate(cnfPred, rangetable, relationId);
} }
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.109 2001/07/16 05:06:58 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.110 2001/08/06 18:09:45 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -79,10 +79,10 @@ static bool match_clause_to_indexkey(RelOptInfo *rel, IndexOptInfo *index, ...@@ -79,10 +79,10 @@ static bool match_clause_to_indexkey(RelOptInfo *rel, IndexOptInfo *index,
Expr *clause, bool join); Expr *clause, bool join);
static bool pred_test(List *predicate_list, List *restrictinfo_list, static bool pred_test(List *predicate_list, List *restrictinfo_list,
List *joininfo_list); List *joininfo_list);
static bool one_pred_test(Expr *predicate, List *restrictinfo_list); static bool pred_test_restrict_list(Expr *predicate, List *restrictinfo_list);
static bool one_pred_clause_expr_test(Expr *predicate, Node *clause); static bool pred_test_recurse_clause(Expr *predicate, Node *clause);
static bool one_pred_clause_test(Expr *predicate, Node *clause); static bool pred_test_recurse_pred(Expr *predicate, Node *clause);
static bool clause_pred_clause_test(Expr *predicate, Node *clause); static bool pred_test_simple_clause(Expr *predicate, Node *clause);
static void indexable_joinclauses(RelOptInfo *rel, IndexOptInfo *index, static void indexable_joinclauses(RelOptInfo *rel, IndexOptInfo *index,
List *joininfo_list, List *restrictinfo_list, List *joininfo_list, List *restrictinfo_list,
List **clausegroups, List **outerrelids); List **clausegroups, List **outerrelids);
...@@ -197,7 +197,8 @@ create_index_paths(Query *root, RelOptInfo *rel) ...@@ -197,7 +197,8 @@ create_index_paths(Query *root, RelOptInfo *rel)
* merging or final output ordering. * merging or final output ordering.
* *
* If there is a predicate, consider it anyway since the index * If there is a predicate, consider it anyway since the index
* predicate has already been found to match the query. * predicate has already been found to match the query. The
* selectivity of the predicate might alone make the index useful.
*/ */
if (restrictclauses != NIL || if (restrictclauses != NIL ||
useful_pathkeys != NIL || useful_pathkeys != NIL ||
...@@ -959,15 +960,13 @@ indexable_operator(Expr *clause, Oid opclass, Oid relam, ...@@ -959,15 +960,13 @@ indexable_operator(Expr *clause, Oid opclass, Oid relam,
* ANDs in the predicate first, then reduces the qualification * ANDs in the predicate first, then reduces the qualification
* clauses down to their constituent terms, and iterates over ORs * clauses down to their constituent terms, and iterates over ORs
* in the predicate last. This order is important to make the test * in the predicate last. This order is important to make the test
* succeed whenever possible (assuming the predicate has been * succeed whenever possible (assuming the predicate has been converted
* successfully cnfify()-ed). --Nels, Jan '93 * to CNF format). --Nels, Jan '93
*/ */
static bool static bool
pred_test(List *predicate_list, List *restrictinfo_list, List *joininfo_list) pred_test(List *predicate_list, List *restrictinfo_list, List *joininfo_list)
{ {
List *pred, List *pred;
*items,
*item;
/* /*
* Note: if Postgres tried to optimize queries by forming equivalence * Note: if Postgres tried to optimize queries by forming equivalence
...@@ -977,6 +976,9 @@ pred_test(List *predicate_list, List *restrictinfo_list, List *joininfo_list) ...@@ -977,6 +976,9 @@ pred_test(List *predicate_list, List *restrictinfo_list, List *joininfo_list)
* here with joininfo_list to do more complete tests for the usability * here with joininfo_list to do more complete tests for the usability
* of a partial index. For now, the test only uses restriction * of a partial index. For now, the test only uses restriction
* clauses (those in restrictinfo_list). --Nels, Dec '92 * clauses (those in restrictinfo_list). --Nels, Dec '92
*
* XXX as of 7.1, equivalence class info *is* available. Consider
* improving this code as foreseen by Nels.
*/ */
if (predicate_list == NIL) if (predicate_list == NIL)
...@@ -989,19 +991,10 @@ pred_test(List *predicate_list, List *restrictinfo_list, List *joininfo_list) ...@@ -989,19 +991,10 @@ pred_test(List *predicate_list, List *restrictinfo_list, List *joininfo_list)
{ {
/* /*
* if any clause is not implied, the whole predicate is not * if any clause is not implied, the whole predicate is not
* implied. Note that checking for sub-ANDs here is redundant * implied. Note we assume that any sub-ANDs have been flattened
* if the predicate has been cnfify()-ed. * when the predicate was fed through canonicalize_qual().
*/ */
if (and_clause(lfirst(pred))) if (!pred_test_restrict_list(lfirst(pred), restrictinfo_list))
{
items = ((Expr *) lfirst(pred))->args;
foreach(item, items)
{
if (!one_pred_test(lfirst(item), restrictinfo_list))
return false;
}
}
else if (!one_pred_test(lfirst(pred), restrictinfo_list))
return false; return false;
} }
return true; return true;
...@@ -1009,22 +1002,21 @@ pred_test(List *predicate_list, List *restrictinfo_list, List *joininfo_list) ...@@ -1009,22 +1002,21 @@ pred_test(List *predicate_list, List *restrictinfo_list, List *joininfo_list)
/* /*
* one_pred_test * pred_test_restrict_list
* Does the "predicate inclusion test" for one conjunct of a predicate * Does the "predicate inclusion test" for one conjunct of a predicate
* expression. * expression.
*/ */
static bool static bool
one_pred_test(Expr *predicate, List *restrictinfo_list) pred_test_restrict_list(Expr *predicate, List *restrictinfo_list)
{ {
List *item; List *item;
Assert(predicate != NULL);
foreach(item, restrictinfo_list) foreach(item, restrictinfo_list)
{ {
RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(item); RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(item);
/* if any clause implies the predicate, return true */ /* if any clause implies the predicate, return true */
if (one_pred_clause_expr_test(predicate, if (pred_test_recurse_clause(predicate,
(Node *) restrictinfo->clause)) (Node *) restrictinfo->clause))
return true; return true;
} }
...@@ -1033,25 +1025,25 @@ one_pred_test(Expr *predicate, List *restrictinfo_list) ...@@ -1033,25 +1025,25 @@ one_pred_test(Expr *predicate, List *restrictinfo_list)
/* /*
* one_pred_clause_expr_test * pred_test_recurse_clause
* Does the "predicate inclusion test" for a general restriction-clause * Does the "predicate inclusion test" for a general restriction-clause
* expression. * expression. Here we recursively deal with the possibility that the
* restriction clause is itself an AND or OR structure.
*/ */
static bool static bool
one_pred_clause_expr_test(Expr *predicate, Node *clause) pred_test_recurse_clause(Expr *predicate, Node *clause)
{ {
List *items, List *items,
*item; *item;
if (is_opclause(clause)) Assert(clause != NULL);
return one_pred_clause_test(predicate, clause); if (or_clause(clause))
else if (or_clause(clause))
{ {
items = ((Expr *) clause)->args; items = ((Expr *) clause)->args;
foreach(item, items) foreach(item, items)
{ {
/* if any OR item doesn't imply the predicate, clause doesn't */ /* if any OR item doesn't imply the predicate, clause doesn't */
if (!one_pred_clause_expr_test(predicate, lfirst(item))) if (!pred_test_recurse_clause(predicate, lfirst(item)))
return false; return false;
} }
return true; return true;
...@@ -1065,39 +1057,37 @@ one_pred_clause_expr_test(Expr *predicate, Node *clause) ...@@ -1065,39 +1057,37 @@ one_pred_clause_expr_test(Expr *predicate, Node *clause)
* if any AND item implies the predicate, the whole clause * if any AND item implies the predicate, the whole clause
* does * does
*/ */
if (one_pred_clause_expr_test(predicate, lfirst(item))) if (pred_test_recurse_clause(predicate, lfirst(item)))
return true; return true;
} }
return false; return false;
} }
else else
{ return pred_test_recurse_pred(predicate, clause);
/* unknown clause type never implies the predicate */
return false;
}
} }
/* /*
* one_pred_clause_test * pred_test_recurse_pred
* Does the "predicate inclusion test" for one conjunct of a predicate * Does the "predicate inclusion test" for one conjunct of a predicate
* expression for a simple restriction clause. * expression for a simple restriction clause. Here we recursively deal
* with the possibility that the predicate conjunct is itself an AND or
* OR structure.
*/ */
static bool static bool
one_pred_clause_test(Expr *predicate, Node *clause) pred_test_recurse_pred(Expr *predicate, Node *clause)
{ {
List *items, List *items,
*item; *item;
if (is_opclause((Node *) predicate)) Assert(predicate != NULL);
return clause_pred_clause_test(predicate, clause); if (or_clause((Node *) predicate))
else if (or_clause((Node *) predicate))
{ {
items = predicate->args; items = predicate->args;
foreach(item, items) foreach(item, items)
{ {
/* if any item is implied, the whole predicate is implied */ /* if any item is implied, the whole predicate is implied */
if (one_pred_clause_test(lfirst(item), clause)) if (pred_test_recurse_pred(lfirst(item), clause))
return true; return true;
} }
return false; return false;
...@@ -1111,16 +1101,13 @@ one_pred_clause_test(Expr *predicate, Node *clause) ...@@ -1111,16 +1101,13 @@ one_pred_clause_test(Expr *predicate, Node *clause)
* if any item is not implied, the whole predicate is not * if any item is not implied, the whole predicate is not
* implied * implied
*/ */
if (!one_pred_clause_test(lfirst(item), clause)) if (!pred_test_recurse_pred(lfirst(item), clause))
return false; return false;
} }
return true; return true;
} }
else else
{ return pred_test_simple_clause(predicate, clause);
elog(DEBUG, "Unsupported predicate type, index will not be used");
return false;
}
} }
...@@ -1156,17 +1143,26 @@ static const StrategyNumber ...@@ -1156,17 +1143,26 @@ static const StrategyNumber
/* /*
* clause_pred_clause_test * pred_test_simple_clause
* Use operator class info to check whether clause implies predicate.
*
* Does the "predicate inclusion test" for a "simple clause" predicate * Does the "predicate inclusion test" for a "simple clause" predicate
* for a single "simple clause" restriction. Currently, this only handles * and a "simple clause" restriction.
* (binary boolean) operators that are in some btree operator class. *
* We have two strategies for determining whether one simple clause
* implies another. A simple and general way is to see if they are
* equal(); this works for any kind of expression. (Actually, there
* is an implied assumption that the functions in the expression are
* cachable, ie dependent only on their input arguments --- but this
* was checked for the predicate by CheckPredicate().)
*
* Our other way works only for (binary boolean) operators that are
* in some btree operator class. We use the above operator implication
* table to be able to derive implications between nonidentical clauses.
*
* Eventually, rtree operators could also be handled by defining an * Eventually, rtree operators could also be handled by defining an
* appropriate "RT_implic_table" array. * appropriate "RT_implic_table" array.
*/ */
static bool static bool
clause_pred_clause_test(Expr *predicate, Node *clause) pred_test_simple_clause(Expr *predicate, Node *clause)
{ {
Var *pred_var, Var *pred_var,
*clause_var; *clause_var;
...@@ -1190,13 +1186,21 @@ clause_pred_clause_test(Expr *predicate, Node *clause) ...@@ -1190,13 +1186,21 @@ clause_pred_clause_test(Expr *predicate, Node *clause)
Form_pg_amop aform; Form_pg_amop aform;
ExprContext *econtext; ExprContext *econtext;
/* Check the basic form; for now, only allow the simplest case */ /* First try the equal() test */
/* Note caller already verified is_opclause(predicate) */ if (equal((Node *) predicate, clause))
if (!is_opclause(clause)) return true;
return false;
/*
* Can't do anything more unless they are both binary opclauses with
* a Var on the left and a Const on the right.
*/
if (!is_opclause((Node *) predicate))
return false;
pred_var = (Var *) get_leftop(predicate); pred_var = (Var *) get_leftop(predicate);
pred_const = (Const *) get_rightop(predicate); pred_const = (Const *) get_rightop(predicate);
if (!is_opclause(clause))
return false;
clause_var = (Var *) get_leftop((Expr *) clause); clause_var = (Var *) get_leftop((Expr *) clause);
clause_const = (Const *) get_rightop((Expr *) clause); clause_const = (Const *) get_rightop((Expr *) clause);
...@@ -1212,7 +1216,8 @@ clause_pred_clause_test(Expr *predicate, Node *clause) ...@@ -1212,7 +1216,8 @@ clause_pred_clause_test(Expr *predicate, Node *clause)
* The implication can't be determined unless the predicate and the * The implication can't be determined unless the predicate and the
* clause refer to the same attribute. * clause refer to the same attribute.
*/ */
if (clause_var->varattno != pred_var->varattno) if (clause_var->varno != pred_var->varno ||
clause_var->varattno != pred_var->varattno)
return false; return false;
/* Get the operators for the two clauses we're comparing */ /* Get the operators for the two clauses we're comparing */
...@@ -1250,15 +1255,16 @@ clause_pred_clause_test(Expr *predicate, Node *clause) ...@@ -1250,15 +1255,16 @@ clause_pred_clause_test(Expr *predicate, Node *clause)
tuple = heap_getnext(scan, 0); tuple = heap_getnext(scan, 0);
if (!HeapTupleIsValid(tuple)) if (!HeapTupleIsValid(tuple))
{ {
elog(DEBUG, "clause_pred_clause_test: unknown pred_op"); /* predicate operator isn't btree-indexable */
heap_endscan(scan); heap_endscan(scan);
heap_close(relation, AccessShareLock); heap_close(relation, AccessShareLock);
return false; return false;
} }
aform = (Form_pg_amop) GETSTRUCT(tuple); aform = (Form_pg_amop) GETSTRUCT(tuple);
/* Get the predicate operator's strategy number (1 to 5) */ /* Get the predicate operator's btree strategy number (1 to 5) */
pred_strategy = (StrategyNumber) aform->amopstrategy; pred_strategy = (StrategyNumber) aform->amopstrategy;
Assert(pred_strategy >= 1 && pred_strategy <= 5);
/* Remember which operator class this strategy number came from */ /* Remember which operator class this strategy number came from */
opclass_id = aform->amopclaid; opclass_id = aform->amopclaid;
...@@ -1282,7 +1288,7 @@ clause_pred_clause_test(Expr *predicate, Node *clause) ...@@ -1282,7 +1288,7 @@ clause_pred_clause_test(Expr *predicate, Node *clause)
tuple = heap_getnext(scan, 0); tuple = heap_getnext(scan, 0);
if (!HeapTupleIsValid(tuple)) if (!HeapTupleIsValid(tuple))
{ {
elog(DEBUG, "clause_pred_clause_test: unknown clause_op"); /* clause operator isn't btree-indexable, or isn't in this opclass */
heap_endscan(scan); heap_endscan(scan);
heap_close(relation, AccessShareLock); heap_close(relation, AccessShareLock);
return false; return false;
...@@ -1291,6 +1297,7 @@ clause_pred_clause_test(Expr *predicate, Node *clause) ...@@ -1291,6 +1297,7 @@ clause_pred_clause_test(Expr *predicate, Node *clause)
/* Get the restriction clause operator's strategy number (1 to 5) */ /* Get the restriction clause operator's strategy number (1 to 5) */
clause_strategy = (StrategyNumber) aform->amopstrategy; clause_strategy = (StrategyNumber) aform->amopstrategy;
Assert(clause_strategy >= 1 && clause_strategy <= 5);
heap_endscan(scan); heap_endscan(scan);
...@@ -1316,7 +1323,8 @@ clause_pred_clause_test(Expr *predicate, Node *clause) ...@@ -1316,7 +1323,8 @@ clause_pred_clause_test(Expr *predicate, Node *clause)
tuple = heap_getnext(scan, 0); tuple = heap_getnext(scan, 0);
if (!HeapTupleIsValid(tuple)) if (!HeapTupleIsValid(tuple))
{ {
elog(DEBUG, "clause_pred_clause_test: unknown test_op"); /* this probably shouldn't fail? */
elog(DEBUG, "pred_test_simple_clause: unknown test_op");
heap_endscan(scan); heap_endscan(scan);
heap_close(relation, AccessShareLock); heap_close(relation, AccessShareLock);
return false; return false;
...@@ -1342,12 +1350,13 @@ clause_pred_clause_test(Expr *predicate, Node *clause) ...@@ -1342,12 +1350,13 @@ clause_pred_clause_test(Expr *predicate, Node *clause)
(Var *) pred_const); (Var *) pred_const);
econtext = MakeExprContext(NULL, TransactionCommandContext); econtext = MakeExprContext(NULL, TransactionCommandContext);
test_result = ExecEvalExpr((Node *) test_expr, econtext, &isNull, NULL); test_result = ExecEvalExprSwitchContext((Node *) test_expr, econtext,
&isNull, NULL);
FreeExprContext(econtext); FreeExprContext(econtext);
if (isNull) if (isNull)
{ {
elog(DEBUG, "clause_pred_clause_test: null test result"); elog(DEBUG, "pred_test_simple_clause: null test result");
return false; return false;
} }
return DatumGetBool(test_result); return DatumGetBool(test_result);
......
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