From 3104a928663d5dc3ff159cb59636845131f1da08 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Wed, 2 Mar 2005 04:10:53 +0000
Subject: [PATCH] Another go at making pred_test() handle all reasonable
 combinations of AND and OR clauses.  The key point here is that an OR on the
 predicate side has to be treated gingerly: we may be able to prove that the
 OR is implied even when no one of its components is implied. For example (x
 OR y) implies (x OR y OR z) even though no one of x, y, or z can be
 individually proven.  This code handles both the example shown recently by
 Sergey Koshcheyev and the one shown last October by Dawid Kuroczko.

---
 src/backend/optimizer/path/indxpath.c | 275 +++++++++++++++-----------
 1 file changed, 158 insertions(+), 117 deletions(-)

diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index ec28bf0408d..f86206304c1 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.168 2005/03/01 00:24:52 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.169 2005/03/02 04:10:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -64,9 +64,7 @@ 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_restrict_list(Expr *predicate, List *restrictinfo_list);
-static bool pred_test_recurse_restrict(Expr *predicate, Node *clause);
-static bool pred_test_recurse_pred(Expr *predicate, Node *clause);
+static bool pred_test_recurse(Node *clause, Node *predicate);
 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,
@@ -749,30 +747,17 @@ 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 breaks down the restriction list
- *	  to its constituent AND/OR elements, and iterates over ORs
- *	  in the predicate last.  This order is important to make the test
- *	  succeed whenever possible. --Nels, Jan '93
- *
- *	  For example, a restriction (a OR b) certainly implies a predicate
- *	  (a OR b OR c), but no one element of the predicate is individually
- *	  implied by the restriction.  By expanding the predicate ORs last
- *	  we are able to prove that the whole predicate is implied by each arm
- *	  of the restriction.  Conversely consider predicate (a AND b) with
- *	  restriction (a AND b AND c).  This should be implied but we will
- *	  fail to prove it if we dissect the restriction first.
- *
  *	  The top-level List structure of each list corresponds to an AND list.
- *	  We assume that canonicalize_qual() has been applied and so there
- *	  are no explicit ANDs immediately below the top-level List structure.
- *	  (If this is not true we might fail to prove an implication that is
- *	  valid, but no worse consequences will ensue.)
+ *	  We assume that canonicalize_qual() has been applied and so there are
+ *	  no un-flattened ANDs or ORs (e.g., no AND immediately within an AND,
+ *	  including AND just below the top-level List structure).
+ *	  If this is not true we might fail to prove an implication that is
+ *	  valid, but no worse consequences will ensue.
  */
 bool
 pred_test(List *predicate_list, List *restrictinfo_list)
 {
-	ListCell   *pred;
+	ListCell   *item;
 
 	/*
 	 * Note: if Postgres tried to optimize queries by forming equivalence
@@ -793,133 +778,189 @@ pred_test(List *predicate_list, List *restrictinfo_list)
 		return false;			/* no restriction clauses: the test must
 								 * fail */
 
-	/* Take care of the AND semantics of the top-level predicate list */
-	foreach(pred, predicate_list)
+	/*
+	 * In all cases where the predicate is an AND-clause, pred_test_recurse()
+	 * will prefer to iterate over the predicate's components.  So we can
+	 * just do that to start with here, and eliminate the need for
+	 * pred_test_recurse() to handle a bare List on the predicate side.
+	 *
+	 * Logic is: restriction must imply each of the AND'ed predicate items.
+	 */
+	foreach(item, predicate_list)
 	{
-		/*
-		 * if any clause is not implied, the whole predicate is not
-		 * implied.
-		 */
-		if (!pred_test_restrict_list(lfirst(pred), restrictinfo_list))
+		if (!pred_test_recurse((Node *) restrictinfo_list, lfirst(item)))
 			return false;
 	}
 	return true;
 }
 
 
-/*
- * pred_test_restrict_list
- *	  Does the "predicate inclusion test" for one AND clause 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)
-{
-	ListCell   *item;
-
-	foreach(item, restrictinfo_list)
-	{
-		/* if any clause implies the predicate, return true */
-		if (pred_test_recurse_restrict(predicate,
-									   (Node *) lfirst(item)))
-			return true;
-	}
-	return false;
-}
-
-
-/*
- * pred_test_recurse_restrict
- *	  Does the "predicate inclusion test" for one AND clause of a predicate
- *	  expression.  Here we recursively deal with the possibility that the
- *	  restriction-list element is itself an AND or OR structure; also,
- *	  we strip off RestrictInfo nodes to find bare qualifier expressions.
+/*----------
+ * pred_test_recurse
+ *	  Does the "predicate inclusion test" for non-NULL restriction and
+ *	  predicate clauses.
+ *
+ * The logic followed here is ("=>" means "implies"):
+ *	atom A => atom B iff:			pred_test_simple_clause says so
+ *	atom A => AND-expr B iff:		A => each of B's components
+ *	atom A => OR-expr B iff:		A => any of B's components
+ *	AND-expr A => atom B iff:		any of A's components => B
+ *	AND-expr A => AND-expr B iff:	A => each of B's components
+ *	AND-expr A => OR-expr B iff:	A => any of B's components,
+ *									*or* any of A's components => B
+ *	OR-expr A => atom B iff:		each of A's components => B
+ *	OR-expr A => AND-expr B iff:	A => each of B's components
+ *	OR-expr A => OR-expr B iff:		each of A's components => any of B's
+ *
+ * An "atom" is anything other than an AND or OR node.  Notice that we don't
+ * have any special logic to handle NOT nodes; these should have been pushed
+ * down or eliminated where feasible by prepqual.c.
+ *
+ * We can't recursively expand either side first, but have to interleave
+ * the expansions per the above rules, to be sure we handle all of these
+ * examples:
+ *		(x OR y) => (x OR y OR z)
+ *		(x AND y AND z) => (x AND y)
+ *		(x AND y) => ((x AND y) OR z)
+ *		((x OR y) AND z) => (x OR y)
+ * This is still not an exhaustive test, but it handles most normal cases
+ * under the assumption that both inputs have been AND/OR flattened.
+ *
+ * A bare List node on the restriction side is interpreted as an AND clause,
+ * in order to handle the top-level restriction List properly.  However we
+ * need not consider a List on the predicate side since pred_test() already
+ * expanded it.
+ *
+ * We have to be prepared to handle RestrictInfo nodes in the restrictinfo
+ * tree, though not in the predicate tree.
+ *----------
  */
 static bool
-pred_test_recurse_restrict(Expr *predicate, Node *clause)
+pred_test_recurse(Node *clause, Node *predicate)
 {
-	List	   *items;
 	ListCell   *item;
 
 	Assert(clause != NULL);
+	/* skip through RestrictInfo */
 	if (IsA(clause, RestrictInfo))
 	{
-		RestrictInfo *restrictinfo = (RestrictInfo *) clause;
-
-		return pred_test_recurse_restrict(predicate,
-										  (Node *) restrictinfo->clause);
+		clause = (Node *) ((RestrictInfo *) clause)->clause;
+		Assert(clause != NULL);
+		Assert(!IsA(clause, RestrictInfo));
 	}
-	else if (or_clause(clause))
+	Assert(predicate != NULL);
+
+	/*
+	 * Since a restriction List clause is handled the same as an AND clause,
+	 * we can avoid duplicate code like this:
+	 */
+	if (and_clause(clause))
+		clause = (Node *) ((BoolExpr *) clause)->args;
+
+	if (IsA(clause, List))
 	{
-		items = ((BoolExpr *) clause)->args;
-		foreach(item, items)
+		if (and_clause(predicate))
 		{
-			/* if any OR item doesn't imply the predicate, clause doesn't */
-			if (!pred_test_recurse_restrict(predicate, lfirst(item)))
-				return false;
+			/* AND-clause => AND-clause if A implies each of B's items */
+			foreach(item, ((BoolExpr *) predicate)->args)
+			{
+				if (!pred_test_recurse(clause, lfirst(item)))
+					return false;
+			}
+			return true;
+		}
+		else if (or_clause(predicate))
+		{
+			/* AND-clause => OR-clause if A implies any of B's items */
+			/* Needed to handle (x AND y) => ((x AND y) OR z) */
+			foreach(item, ((BoolExpr *) predicate)->args)
+			{
+				if (pred_test_recurse(clause, lfirst(item)))
+					return true;
+			}
+			/* Also check if any of A's items implies B */
+			/* Needed to handle ((x OR y) AND z) => (x OR y) */
+			foreach(item, (List *) clause)
+			{
+				if (pred_test_recurse(lfirst(item), predicate))
+					return true;
+			}
+			return false;
+		}
+		else
+		{
+			/* AND-clause => atom if any of A's items implies B */
+			foreach(item, (List *) clause)
+			{
+				if (pred_test_recurse(lfirst(item), predicate))
+					return true;
+			}
+			return false;
 		}
-		return true;
 	}
-	else if (and_clause(clause))
+	else if (or_clause(clause))
 	{
-		items = ((BoolExpr *) clause)->args;
-		foreach(item, items)
+		if (or_clause(predicate))
 		{
 			/*
-			 * if any AND item implies the predicate, the whole clause
-			 * does
+			 * OR-clause => OR-clause if each of A's items implies any of
+			 * B's items.  Messy but can't do it any more simply.
 			 */
-			if (pred_test_recurse_restrict(predicate, lfirst(item)))
-				return true;
+			foreach(item, ((BoolExpr *) clause)->args)
+			{
+				Node	   *citem = lfirst(item);
+				ListCell   *item2;
+
+				foreach(item2, ((BoolExpr *) predicate)->args)
+				{
+					if (pred_test_recurse(citem, lfirst(item2)))
+						break;
+				}
+				if (item2 == NULL)
+					return false; /* doesn't imply any of B's */
+			}
+			return true;
+		}
+		else
+		{
+			/* OR-clause => AND-clause if each of A's items implies B */
+			/* OR-clause => atom if each of A's items implies B */
+			foreach(item, ((BoolExpr *) clause)->args)
+			{
+				if (!pred_test_recurse(lfirst(item), predicate))
+					return false;
+			}
+			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.  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 (and_clause(predicate))
 		{
-			/* if any item is implied, the whole predicate is implied */
-			if (pred_test_recurse_pred(lfirst(item), clause))
-				return true;
+			/* atom => AND-clause if A implies each of B's items */
+			foreach(item, ((BoolExpr *) predicate)->args)
+			{
+				if (!pred_test_recurse(clause, lfirst(item)))
+					return false;
+			}
+			return true;
 		}
-		return false;
-	}
-	else if (and_clause((Node *) predicate))
-	{
-		items = ((BoolExpr *) predicate)->args;
-		foreach(item, items)
+		else if (or_clause(predicate))
 		{
-			/*
-			 * if any item is not implied, the whole predicate is not
-			 * implied
-			 */
-			if (!pred_test_recurse_pred(lfirst(item), clause))
-				return false;
+			/* atom => OR-clause if A implies any of B's items */
+			foreach(item, ((BoolExpr *) predicate)->args)
+			{
+				if (pred_test_recurse(clause, lfirst(item)))
+					return true;
+			}
+			return false;
+		}
+		else
+		{
+			/* atom => atom is the base case */
+			return pred_test_simple_clause((Expr *) predicate, clause);
 		}
-		return true;
 	}
-	else
-		return pred_test_simple_clause(predicate, clause);
 }
 
 
-- 
GitLab