From 26112850ec5733a96a31022859763de4b3724336 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Mon, 11 Oct 2004 22:57:00 +0000
Subject: [PATCH] Fix OR-index-scan planner to recognize that a partial index
 is usable 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   | 135 ++++++++++++------------
 src/backend/optimizer/path/orindxpath.c |  37 ++++++-
 src/include/optimizer/paths.h           |   3 +-
 3 files changed, 101 insertions(+), 74 deletions(-)

diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 08aee2010ef..2a0c3d1c5d6 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -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);
 }
diff --git a/src/backend/optimizer/path/orindxpath.c b/src/backend/optimizer/path/orindxpath.c
index c2de25aa4c6..09c2aa161b6 100644
--- a/src/backend/optimizer/path/orindxpath.c
+++ b/src/backend/optimizer/path/orindxpath.c
@@ -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 */
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index b9bfeddaa2e..845f6557613 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -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);
 
-- 
GitLab