]> granicus.if.org Git - postgresql/commitdiff
Fix OR-index-scan planner to recognize that a partial index is usable
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 11 Oct 2004 22:57:00 +0000 (22:57 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 11 Oct 2004 22:57:00 +0000 (22:57 +0000)
for scanning one term of an OR clause if the index's predicate is implied
by that same OR clause term (possibly in conjunction with top-level WHERE
clauses).  Per recent example from Dawid Kuroczko,
http://archives.postgresql.org/pgsql-performance/2004-10/msg00095.php
Also, fix a very long-standing bug in index predicate testing, namely the
bizarre ordering of decomposition of predicate and restriction clauses.
AFAICS the correct way is to break down the predicate all the way, and
then for each component term see if you can prove it from the entire
restriction set.  The original coding had a purely-implementation-artifact
distinction between ANDing at the top level and ANDing below that, and
proceeded to get the decomposition order wrong everywhere below the top
level, with the result that even slightly complicated AND/OR predicates
could not be proven.  For instance, given
create index foop on foo(f2) where f1=42 or f1=1
    or (f1 = 11 and f2 = 55);
the old code would fail to match this index to the query
select * from foo where f1 = 11 and f2 = 55;
when it obviously ought to match.

src/backend/optimizer/path/indxpath.c
src/backend/optimizer/path/orindxpath.c
src/include/optimizer/paths.h

index 08aee2010ef11a28f9823f05afe90a08de257b26..2a0c3d1c5d64b9373f152b343dabae645772e240 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.164 2004/08/29 05:06:43 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.165 2004/10/11 22:56:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -64,10 +64,9 @@ static bool match_join_clause_to_indexcol(RelOptInfo *rel, IndexOptInfo *index,
                                                          RestrictInfo *rinfo);
 static Oid indexable_operator(Expr *clause, Oid opclass,
                                   bool indexkey_on_left);
-static bool pred_test(List *predicate_list, List *restrictinfo_list);
+static bool pred_test_recurse_pred(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_pred(Expr *predicate, Node *clause);
+static bool pred_test_recurse_restrict(Expr *predicate, Node *clause);
 static bool pred_test_simple_clause(Expr *predicate, Node *clause);
 static Relids indexable_outerrelids(RelOptInfo *rel, IndexOptInfo *index);
 static Path *make_innerjoin_index_path(Query *root,
@@ -750,14 +749,13 @@ check_partial_indexes(Query *root, RelOptInfo *rel)
  *       Recursively checks whether the clauses in restrictinfo_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 converted
- *       to CNF format). --Nels, Jan '93
+ *       This routine (together with the routines it calls) first breaks down
+ *       the predicate to its constituent AND/OR elements, then similarly
+ *       breaks down the restriction clauses to AND/OR elements in an effort
+ *       to prove that each predicate element is implied.  The top-level
+ *       List structure of each list corresponds to an AND list.
  */
-static bool
+bool
 pred_test(List *predicate_list, List *restrictinfo_list)
 {
        ListCell   *pred;
@@ -785,10 +783,9 @@ pred_test(List *predicate_list, List *restrictinfo_list)
        {
                /*
                 * if any clause is not implied, the whole predicate is not
-                * implied.  Note we assume that any sub-ANDs have been flattened
-                * when the predicate was fed through canonicalize_qual().
+                * implied.
                 */
-               if (!pred_test_restrict_list(lfirst(pred), restrictinfo_list))
+               if (!pred_test_recurse_pred(lfirst(pred), restrictinfo_list))
                        return false;
        }
        return true;
@@ -796,9 +793,53 @@ pred_test(List *predicate_list, List *restrictinfo_list)
 
 
 /*
- * pred_test_restrict_list
+ * pred_test_recurse_pred
  *       Does the "predicate inclusion test" for one conjunct of a predicate
- *       expression.
+ *       expression.  Here we recursively deal with the possibility that the
+ *       predicate conjunct is itself an AND or OR structure.
+ */
+static bool
+pred_test_recurse_pred(Expr *predicate, List *restrictinfo_list)
+{
+       List       *items;
+       ListCell   *item;
+
+       Assert(predicate != NULL);
+       if (or_clause((Node *) predicate))
+       {
+               items = ((BoolExpr *) predicate)->args;
+               foreach(item, items)
+               {
+                       /* if any item is implied, the whole predicate is implied */
+                       if (pred_test_recurse_pred(lfirst(item), restrictinfo_list))
+                               return true;
+               }
+               return false;
+       }
+       else if (and_clause((Node *) predicate))
+       {
+               items = ((BoolExpr *) predicate)->args;
+               foreach(item, items)
+               {
+                       /*
+                        * if any item is not implied, the whole predicate is not
+                        * implied
+                        */
+                       if (!pred_test_recurse_pred(lfirst(item), restrictinfo_list))
+                               return false;
+               }
+               return true;
+       }
+       else
+               return pred_test_restrict_list(predicate, restrictinfo_list);
+}
+
+
+/*
+ * pred_test_restrict_list
+ *       Does the "predicate inclusion test" for one element of a predicate
+ *       expression.  Here we take care of the AND semantics of the top-level
+ *       restrictinfo list.
  */
 static bool
 pred_test_restrict_list(Expr *predicate, List *restrictinfo_list)
@@ -809,9 +850,11 @@ pred_test_restrict_list(Expr *predicate, List *restrictinfo_list)
        {
                RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(item);
 
+               Assert(IsA(restrictinfo, RestrictInfo));
+
                /* if any clause implies the predicate, return true */
-               if (pred_test_recurse_clause(predicate,
-                                                                        (Node *) restrictinfo->clause))
+               if (pred_test_recurse_restrict(predicate,
+                                                                          (Node *) restrictinfo->clause))
                        return true;
        }
        return false;
@@ -819,13 +862,13 @@ pred_test_restrict_list(Expr *predicate, List *restrictinfo_list)
 
 
 /*
- * pred_test_recurse_clause
- *       Does the "predicate inclusion test" for a general restriction-clause
+ * pred_test_recurse_restrict
+ *       Does the "predicate inclusion test" for one element of a predicate
  *       expression.  Here we recursively deal with the possibility that the
- *       restriction clause is itself an AND or OR structure.
+ *       restriction-list element is itself an AND or OR structure.
  */
 static bool
-pred_test_recurse_clause(Expr *predicate, Node *clause)
+pred_test_recurse_restrict(Expr *predicate, Node *clause)
 {
        List       *items;
        ListCell   *item;
@@ -837,7 +880,7 @@ pred_test_recurse_clause(Expr *predicate, Node *clause)
                foreach(item, items)
                {
                        /* if any OR item doesn't imply the predicate, clause doesn't */
-                       if (!pred_test_recurse_clause(predicate, lfirst(item)))
+                       if (!pred_test_recurse_restrict(predicate, lfirst(item)))
                                return false;
                }
                return true;
@@ -851,55 +894,11 @@ pred_test_recurse_clause(Expr *predicate, Node *clause)
                         * if any AND item implies the predicate, the whole clause
                         * does
                         */
-                       if (pred_test_recurse_clause(predicate, lfirst(item)))
+                       if (pred_test_recurse_restrict(predicate, lfirst(item)))
                                return true;
                }
                return false;
        }
-       else
-               return pred_test_recurse_pred(predicate, clause);
-}
-
-
-/*
- * pred_test_recurse_pred
- *       Does the "predicate inclusion test" for one conjunct of a predicate
- *       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
-pred_test_recurse_pred(Expr *predicate, Node *clause)
-{
-       List       *items;
-       ListCell   *item;
-
-       Assert(predicate != NULL);
-       if (or_clause((Node *) predicate))
-       {
-               items = ((BoolExpr *) predicate)->args;
-               foreach(item, items)
-               {
-                       /* if any item is implied, the whole predicate is implied */
-                       if (pred_test_recurse_pred(lfirst(item), clause))
-                               return true;
-               }
-               return false;
-       }
-       else if (and_clause((Node *) predicate))
-       {
-               items = ((BoolExpr *) predicate)->args;
-               foreach(item, items)
-               {
-                       /*
-                        * if any item is not implied, the whole predicate is not
-                        * implied
-                        */
-                       if (!pred_test_recurse_pred(lfirst(item), clause))
-                               return false;
-               }
-               return true;
-       }
        else
                return pred_test_simple_clause(predicate, clause);
 }
index c2de25aa4c6a39b80bb56bae780af10337a74c99..09c2aa161b620af6b8224825e9fe6df1d32951a9 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/path/orindxpath.c,v 1.62 2004/08/29 05:06:43 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/path/orindxpath.c,v 1.63 2004/10/11 22:56:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -355,15 +355,42 @@ best_or_subclause_index(Query *root,
                List       *indexquals;
                Path            subclause_path;
 
-               /* Ignore partial indexes that do not match the query */
+               /*
+                * Ignore partial indexes that do not match the query.  If predOK
+                * is true then the index's predicate is implied by top-level
+                * restriction clauses, so we can use it.  However, it might also
+                * be implied by the current OR subclause (perhaps in conjunction
+                * with the top-level clauses), in which case we can use it for this
+                * particular scan.
+                *
+                * XXX this code is partially redundant with logic in
+                * group_clauses_by_indexkey_for_or(); consider refactoring.
+                */
                if (index->indpred != NIL && !index->predOK)
-                       continue;
+               {
+                       List   *subclauserinfos;
+
+                       if (and_clause((Node *) subclause))
+                               subclauserinfos = list_copy(((BoolExpr *) subclause)->args);
+                       else if (IsA(subclause, RestrictInfo))
+                               subclauserinfos = list_make1(subclause);
+                       else
+                               continue;               /* probably can't happen */
+                       if (!pred_test(index->indpred,
+                                                  list_concat(subclauserinfos, 
+                                                                          rel->baserestrictinfo)))
+                               continue;
+               }
 
                /* Collect index clauses usable with this index */
                indexclauses = group_clauses_by_indexkey_for_or(rel, index, subclause);
 
-               /* Ignore index if it doesn't match the subclause at all */
-               if (indexclauses == NIL)
+               /*
+                * Ignore index if it doesn't match the subclause at all; except
+                * that if it's a partial index, consider it anyway, since the
+                * selectivity of the predicate alone might make the index useful.
+                */
+               if (indexclauses == NIL && index->indpred == NIL)
                        continue;
 
                /* Convert clauses to indexquals the executor can handle */
index b9bfeddaa2ef560a0c50ff5b2f5aee0f8df826d5..845f6557613cc117367b789d978a973c4ddbff36 100644 (file)
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/optimizer/paths.h,v 1.75 2004/08/29 05:06:57 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/paths.h,v 1.76 2004/10/11 22:57:00 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -44,6 +44,7 @@ extern List *group_clauses_by_indexkey_for_or(RelOptInfo *rel,
 extern List *expand_indexqual_conditions(IndexOptInfo *index,
                                                        List *clausegroups);
 extern void check_partial_indexes(Query *root, RelOptInfo *rel);
+extern bool pred_test(List *predicate_list, List *restrictinfo_list);
 extern List *flatten_clausegroups_list(List *clausegroups);
 extern Expr *make_expr_from_indexclauses(List *indexclauses);