From 2119cc0670c90f0c857137d1ca37c99997e31290 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sun, 12 Sep 1999 18:08:17 +0000
Subject: [PATCH] Further improvements in cnfify: reduce amount of
 self-recursion in or_normalize, remove detection of duplicate subexpressions
 (since it's highly unlikely to be worth the amount of time it takes), and
 introduce a dnfify() entry point so that unintelligible backwards logic in
 UNION processing can be eliminated.  This is just an intermediate step ---
 next thing is to look at not forcing the qual into CNF form when it would be
 better off in DNF form.

---
 src/backend/optimizer/prep/prepqual.c | 713 ++++++++++++++------------
 src/include/optimizer/prep.h          |   5 +-
 2 files changed, 384 insertions(+), 334 deletions(-)

diff --git a/src/backend/optimizer/prep/prepqual.c b/src/backend/optimizer/prep/prepqual.c
index 974884d8bcc..72e26f35074 100644
--- a/src/backend/optimizer/prep/prepqual.c
+++ b/src/backend/optimizer/prep/prepqual.c
@@ -1,13 +1,13 @@
 /*-------------------------------------------------------------------------
  *
  * prepqual.c
- *	  Routines for preprocessing the parse tree qualification
+ *	  Routines for preprocessing qualification expressions
  *
  * Copyright (c) 1994, Regents of the University of California
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepqual.c,v 1.18 1999/09/07 03:47:06 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepqual.c,v 1.19 1999/09/12 18:08:17 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -20,28 +20,33 @@
 #include "optimizer/prep.h"
 #include "utils/lsyscache.h"
 
-static Expr *flatten_andors(Expr *qual, bool deep);
+static Expr *flatten_andors(Expr *qual);
 static List *pull_ors(List *orlist);
 static List *pull_ands(List *andlist);
 static Expr *find_nots(Expr *qual);
 static Expr *push_nots(Expr *qual);
-static Expr *normalize(Expr *qual);
-static List *or_normalize(List *orlist);
-static List *distribute_args(List *item, List *args);
-static List *qual_cleanup(Expr *qual);
-static List *remove_duplicates(List *list);
+static Expr *find_ors(Expr *qual);
+static Expr *or_normalize(List *orlist);
+static Expr *find_ands(Expr *qual);
+static Expr *and_normalize(List *andlist);
 
 /*****************************************************************************
  *
- *		CNF CONVERSION ROUTINES
+ *		CNF/DNF CONVERSION ROUTINES
  *
- *		NOTES:
- *		The basic algorithms for normalizing the qualification are taken
- *		from ingres/source/qrymod/norml.c
+ *		These routines convert an arbitrary boolean expression into
+ *		conjunctive normal form or disjunctive normal form.
  *
- *		Remember that the initial qualification may consist of ARBITRARY
- *		combinations of clauses.  In addition, before this routine is called,
- *		the qualification will contain explicit "AND"s.
+ *		The result of these routines differs from a "true" CNF/DNF in that
+ *		we do not bother to detect common subexpressions; e.g., ("AND" A A)
+ *		does not get simplified to A.  Testing for identical subexpressions
+ *		is a waste of time if the query is written intelligently, and it
+ *		takes an unreasonable amount of time if there are many subexpressions
+ *		(since it's roughly O(N^2) in the number of subexpressions).
+ *
+ *		Because of that restriction, it would be unwise to apply dnfify()
+ *		to the result of cnfify() or vice versa.  Instead apply both to
+ *		the original user-written qual expression.
  *
  *****************************************************************************/
 
@@ -54,44 +59,225 @@ static List *remove_duplicates(List *list);
  * Returns the modified qualification.
  *
  * If 'removeAndFlag' is true then it removes explicit AND at the top level,
- * producing a list of implicitly-ANDed conditions.  Otherwise, a normal
- * boolean expression is returned.
- *
- * NOTE: this routine is called by the planner (removeAndFlag = true)
- *		and from the rule manager (removeAndFlag = false).
- *
+ * producing a list of implicitly-ANDed conditions.  Otherwise, a regular
+ * boolean expression is returned.  Since most callers pass 'true', we
+ * prefer to declare the result as List *, not Expr *.
  */
 List *
 cnfify(Expr *qual, bool removeAndFlag)
 {
-	Expr	   *newqual = NULL;
+	Expr	   *newqual;
 
-	if (qual != NULL)
+	if (qual == NULL)
+		return NIL;
+
+	/* Flatten AND and OR groups throughout the tree.
+	 * This improvement is always worthwhile.
+	 */
+	newqual = flatten_andors(qual);
+	/* Push down NOTs.  We do this only in the top-level boolean
+	 * expression, without examining arguments of operators/functions.
+	 */
+	newqual = find_nots(newqual);
+	/* Normalize into conjunctive normal form. */
+	newqual = find_ors(newqual);
+
+	if (removeAndFlag)
 	{
-		/* Flatten AND and OR groups throughout the tree.
-		 * This improvement is always worthwhile.
-		 */
-		newqual = flatten_andors(qual, true);
-		/* Push down NOTs.  We do this only in the top-level boolean
-		 * expression, without examining arguments of operators/functions.
-		 */
-		newqual = find_nots(newqual);
-		/* Pushing NOTs could have brought AND/ORs together, so do
-		 * another flatten_andors (only in the top level); then normalize.
-		 */
-		newqual = normalize(flatten_andors(newqual, false));
-		/* Do we need a flatten here?  Anyway, clean up after normalize. */
-		newqual = (Expr *) qual_cleanup(flatten_andors(newqual, false));
-		/* This flatten is almost surely a waste of time... */
-		newqual = flatten_andors(newqual, false);
+		newqual = (Expr *) make_ands_implicit(newqual);
+	}
+
+	return (List *) newqual;
+}
+
+/*
+ * dnfify
+ *	  Convert a qualification to disjunctive normal form by applying
+ *	  successive normalizations.
+ *
+ * Returns the modified qualification.
+ *
+ * We do not offer a 'removeOrFlag' in this case; the usages are
+ * different.
+ */
+Expr *
+dnfify(Expr *qual)
+{
+	Expr	   *newqual;
+
+	if (qual == NULL)
+		return NULL;
+
+	/* Flatten AND and OR groups throughout the tree.
+	 * This improvement is always worthwhile.
+	 */
+	newqual = flatten_andors(qual);
+	/* Push down NOTs.  We do this only in the top-level boolean
+	 * expression, without examining arguments of operators/functions.
+	 */
+	newqual = find_nots(newqual);
+	/* Normalize into disjunctive normal form. */
+	newqual = find_ands(newqual);
 
-		if (removeAndFlag)
+	return newqual;
+}
+
+/*--------------------
+ * The parser regards AND and OR as purely binary operators, so a qual like
+ *		(A = 1) OR (A = 2) OR (A = 3) ...
+ * will produce a nested parsetree
+ *		(OR (A = 1) (OR (A = 2) (OR (A = 3) ...)))
+ * In reality, the optimizer and executor regard AND and OR as n-argument
+ * operators, so this tree can be flattened to
+ *		(OR (A = 1) (A = 2) (A = 3) ...)
+ * which is the responsibility of the routines below.
+ *
+ * flatten_andors() does the basic transformation with no initial assumptions.
+ * pull_ands() and pull_ors() are used to maintain flatness of the AND/OR
+ * tree after local transformations that might introduce nested AND/ORs.
+ *--------------------
+ */
+
+/*--------------------
+ * flatten_andors
+ *	  Given a qualification, simplify nested AND/OR clauses into flat
+ *	  AND/OR clauses with more arguments.
+ *
+ * Returns the rebuilt expr (note original list structure is not touched).
+ *--------------------
+ */
+static Expr *
+flatten_andors(Expr *qual)
+{
+	if (qual == NULL)
+		return NULL;
+
+	if (and_clause((Node *) qual))
+	{
+		List	   *out_list = NIL;
+		List	   *arg;
+
+		foreach(arg, qual->args)
 		{
-			newqual = (Expr *) make_ands_implicit(newqual);
+			Expr   *subexpr = flatten_andors((Expr *) lfirst(arg));
+
+			/*
+			 * Note: we can destructively nconc the subexpression's arglist
+			 * because we know the recursive invocation of flatten_andors
+			 * will have built a new arglist not shared with any other expr.
+			 * Otherwise we'd need a listCopy here.
+			 */
+			if (and_clause((Node *) subexpr))
+				out_list = nconc(out_list, subexpr->args);
+			else
+				out_list = lappend(out_list, subexpr);
 		}
+		return make_andclause(out_list);
+	}
+	else if (or_clause((Node *) qual))
+	{
+		List	   *out_list = NIL;
+		List	   *arg;
+
+		foreach(arg, qual->args)
+		{
+			Expr   *subexpr = flatten_andors((Expr *) lfirst(arg));
+
+			/*
+			 * Note: we can destructively nconc the subexpression's arglist
+			 * because we know the recursive invocation of flatten_andors
+			 * will have built a new arglist not shared with any other expr.
+			 * Otherwise we'd need a listCopy here.
+			 */
+			if (or_clause((Node *) subexpr))
+				out_list = nconc(out_list, subexpr->args);
+			else
+				out_list = lappend(out_list, subexpr);
+		}
+		return make_orclause(out_list);
+	}
+	else if (not_clause((Node *) qual))
+		return make_notclause(flatten_andors(get_notclausearg(qual)));
+	else if (is_opclause((Node *) qual))
+	{
+		Expr	   *left = (Expr *) get_leftop(qual);
+		Expr	   *right = (Expr *) get_rightop(qual);
+
+		if (right)
+			return make_clause(qual->opType, qual->oper,
+							   lcons(flatten_andors(left),
+									 lcons(flatten_andors(right),
+										   NIL)));
+		else
+			return make_clause(qual->opType, qual->oper,
+							   lcons(flatten_andors(left),
+									 NIL));
 	}
+	else
+		return qual;
+}
+
+/*
+ * pull_ors
+ *	  Pull the arguments of an 'or' clause nested within another 'or'
+ *	  clause up into the argument list of the parent.
+ *
+ * Input is the arglist of an OR clause.
+ * Returns the rebuilt arglist (note original list structure is not touched).
+ */
+static List *
+pull_ors(List *orlist)
+{
+	List	   *out_list = NIL;
+	List	   *arg;
 
-	return (List *) (newqual);
+	foreach(arg, orlist)
+	{
+		Expr   *subexpr = (Expr *) lfirst(arg);
+
+		/*
+		 * Note: we can destructively nconc the subexpression's arglist
+		 * because we know the recursive invocation of pull_ors
+		 * will have built a new arglist not shared with any other expr.
+		 * Otherwise we'd need a listCopy here.
+		 */
+		if (or_clause((Node *) subexpr))
+			out_list = nconc(out_list, pull_ors(subexpr->args));
+		else
+			out_list = lappend(out_list, subexpr);
+	}
+	return out_list;
+}
+
+/*
+ * pull_ands
+ *	  Pull the arguments of an 'and' clause nested within another 'and'
+ *	  clause up into the argument list of the parent.
+ *
+ * Returns the modified list.
+ */
+static List *
+pull_ands(List *andlist)
+{
+	List	   *out_list = NIL;
+	List	   *arg;
+
+	foreach(arg, andlist)
+	{
+		Expr   *subexpr = (Expr *) lfirst(arg);
+
+		/*
+		 * Note: we can destructively nconc the subexpression's arglist
+		 * because we know the recursive invocation of pull_ands
+		 * will have built a new arglist not shared with any other expr.
+		 * Otherwise we'd need a listCopy here.
+		 */
+		if (and_clause((Node *) subexpr))
+			out_list = nconc(out_list, pull_ands(subexpr->args));
+		else
+			out_list = lappend(out_list, subexpr);
+	}
+	return out_list;
 }
 
 /*
@@ -100,8 +286,7 @@ cnfify(Expr *qual, bool removeAndFlag)
  *	  For 'NOT' clauses, apply push_not() to try to push down the 'NOT'.
  *	  For all other clause types, simply recurse.
  *
- * Returns the modified qualification.
- *
+ * Returns the modified qualification.  AND/OR flatness is preserved.
  */
 static Expr *
 find_nots(Expr *qual)
@@ -134,7 +319,7 @@ find_nots(Expr *qual)
 
 		foreach(temp, qual->args)
 			t_list = lappend(t_list, find_nots(lfirst(temp)));
-		return make_andclause(t_list);
+		return make_andclause(pull_ands(t_list));
 	}
 	else if (or_clause((Node *) qual))
 	{
@@ -143,7 +328,7 @@ find_nots(Expr *qual)
 
 		foreach(temp, qual->args)
 			t_list = lappend(t_list, find_nots(lfirst(temp)));
-		return make_orclause(t_list);
+		return make_orclause(pull_ors(t_list));
 	}
 	else if (not_clause((Node *) qual))
 		return push_nots(get_notclausearg(qual));
@@ -187,17 +372,19 @@ push_nots(Expr *qual)
 	}
 	else if (and_clause((Node *) qual))
 	{
-		/*
-		 * Apply DeMorgan's Laws: ("NOT" ("AND" A B)) => ("OR" ("NOT" A)
-		 * ("NOT" B)) ("NOT" ("OR" A B)) => ("AND" ("NOT" A) ("NOT" B))
-		 * i.e., continue negating down through the clause's descendants.
+		/*--------------------
+		 * Apply DeMorgan's Laws:
+		 *		("NOT" ("AND" A B)) => ("OR" ("NOT" A) ("NOT" B))
+		 *		("NOT" ("OR" A B))  => ("AND" ("NOT" A) ("NOT" B))
+		 * i.e., swap AND for OR and negate all the subclauses.
+		 *--------------------
 		 */
 		List	   *t_list = NIL;
 		List	   *temp;
 
 		foreach(temp, qual->args)
 			t_list = lappend(t_list, push_nots(lfirst(temp)));
-		return make_orclause(t_list);
+		return make_orclause(pull_ors(t_list));
 	}
 	else if (or_clause((Node *) qual))
 	{
@@ -206,7 +393,7 @@ push_nots(Expr *qual)
 
 		foreach(temp, qual->args)
 			t_list = lappend(t_list, push_nots(lfirst(temp)));
-		return make_andclause(t_list);
+		return make_andclause(pull_ands(t_list));
 	}
 	else if (not_clause((Node *) qual))
 	{
@@ -228,20 +415,18 @@ push_nots(Expr *qual)
 }
 
 /*
- * normalize
+ * find_ors
  *	  Given a qualification tree with the 'not's pushed down, convert it
  *	  to a tree in CNF by repeatedly applying the rule:
  *				("OR" A ("AND" B C))  => ("AND" ("OR" A B) ("OR" A C))
- *	  bottom-up.
- *	  Note that 'or' clauses will always be turned into 'and' clauses
- *	  if they contain any 'and' subclauses.  XXX this is not always
- *	  an improvement...
  *
- * Returns the modified qualification.
+ *	  Note that 'or' clauses will always be turned into 'and' clauses
+ *	  if they contain any 'and' subclauses.
  *
+ * Returns the modified qualification.  AND/OR flatness is preserved.
  */
 static Expr *
-normalize(Expr *qual)
+find_ors(Expr *qual)
 {
 	if (qual == NULL)
 		return NULL;
@@ -249,346 +434,210 @@ normalize(Expr *qual)
 	/* We used to recurse into opclauses here, but I see no reason to... */
 	if (and_clause((Node *) qual))
 	{
-		List	   *t_list = NIL;
+		List	   *andlist = NIL;
 		List	   *temp;
 
 		foreach(temp, qual->args)
-			t_list = lappend(t_list, normalize(lfirst(temp)));
-		return make_andclause(t_list);
+			andlist = lappend(andlist, find_ors(lfirst(temp)));
+		return make_andclause(pull_ands(andlist));
 	}
 	else if (or_clause((Node *) qual))
 	{
-		/* XXX - let form, maybe incorrect */
 		List	   *orlist = NIL;
-		bool		has_andclause = false;
 		List	   *temp;
 
 		foreach(temp, qual->args)
-			orlist = lappend(orlist, normalize(lfirst(temp)));
-		foreach(temp, orlist)
-		{
-			if (and_clause(lfirst(temp)))
-			{
-				has_andclause = true;
-				break;
-			}
-		}
-		if (has_andclause)
-			return make_andclause(or_normalize(orlist));
-		else
-			return make_orclause(orlist);
+			orlist = lappend(orlist, find_ors(lfirst(temp)));
+		return or_normalize(pull_ors(orlist));
 	}
 	else if (not_clause((Node *) qual))
-		return make_notclause(normalize(get_notclausearg(qual)));
+		return make_notclause(find_ors(get_notclausearg(qual)));
 	else
 		return qual;
 }
 
 /*
- * qual_cleanup
- *	  Fix up a qualification by removing duplicate entries (left over from
- *	  normalization), and by removing 'and' and 'or' clauses which have only
- *	  one remaining subexpr (e.g., ("AND" A) => A).
+ * or_normalize
+ *	  Given a list of exprs which are 'or'ed together, try to apply
+ *	  the distributive law
+ *				("OR" A ("AND" B C))  => ("AND" ("OR" A B) ("OR" A C))
+ *	  to convert the top-level OR clause to a top-level AND clause.
  *
- * Returns the modified qualification.
+ * Returns the resulting expression (could be an AND clause, an OR
+ * clause, or maybe even a single subexpression).
  */
-static List *
-qual_cleanup(Expr *qual)
+static Expr *
+or_normalize(List *orlist)
 {
-	if (qual == NULL)
-		return NIL;
+	Expr	   *distributable = NULL;
+	int			num_subclauses = 1;
+	List	   *andclauses = NIL;
+	List	   *temp;
 
-	if (is_opclause((Node *) qual))
-	{
-		Expr	   *left = (Expr *) get_leftop(qual);
-		Expr	   *right = (Expr *) get_rightop(qual);
+	if (orlist == NIL)
+		return NULL;			/* probably can't happen */
+	if (lnext(orlist) == NIL)
+		return lfirst(orlist);	/* single-expression OR (can this happen?) */
 
-		if (right)
-			return (List *) make_clause(qual->opType, qual->oper,
-										lcons(qual_cleanup(left),
-											  lcons(qual_cleanup(right),
-													NIL)));
-		else
-			return (List *) make_clause(qual->opType, qual->oper,
-										lcons(qual_cleanup(left),
-											  NIL));
-	}
-	else if (and_clause((Node *) qual))
+	/*
+	 * If we have a choice of AND clauses, pick the one with the
+	 * most subclauses.  Because we initialized num_subclauses = 1,
+	 * any AND clauses with only one arg will be ignored as useless.
+	 */
+	foreach(temp, orlist)
 	{
-		List	   *t_list = NIL;
-		List	   *temp;
-		List	   *new_and_args;
-
-		foreach(temp, qual->args)
-			t_list = lappend(t_list, qual_cleanup(lfirst(temp)));
+		Expr   *clause = lfirst(temp);
 
-		new_and_args = remove_duplicates(t_list);
+		if (and_clause((Node *) clause))
+		{
+			int		nclauses = length(clause->args);
 
-		if (length(new_and_args) > 1)
-			return (List *) make_andclause(new_and_args);
-		else
-			return lfirst(new_and_args);
+			if (nclauses > num_subclauses)
+			{
+				distributable = clause;
+				num_subclauses = nclauses;
+			}
+		}
 	}
-	else if (or_clause((Node *) qual))
-	{
-		List	   *t_list = NIL;
-		List	   *temp;
-		List	   *new_or_args;
 
-		foreach(temp, qual->args)
-			t_list = lappend(t_list, qual_cleanup(lfirst(temp)));
+	/* if there's no suitable AND clause, we can't transform the OR */
+	if (! distributable)
+		return make_orclause(orlist);
 
-		new_or_args = remove_duplicates(t_list);
+	/* Caution: lremove destructively modifies the input orlist.
+	 * This should be OK, since or_normalize is only called with
+	 * freshly constructed lists that are not referenced elsewhere.
+	 */
+	orlist = lremove(distributable, orlist);
 
-		if (length(new_or_args) > 1)
-			return (List *) make_orclause(new_or_args);
-		else
-			return lfirst(new_or_args);
+	foreach(temp, distributable->args)
+	{
+		Expr	   *andclause = lfirst(temp);
+
+		/* pull_ors is needed here in case andclause has a top-level OR.
+		 * Then we recursively apply or_normalize, since there might
+		 * be an AND subclause in the resulting OR-list.
+		 * Note: we rely on pull_ors to build a fresh list,
+		 * and not damage the given orlist.
+		 */
+		andclause = or_normalize(pull_ors(lcons(andclause, orlist)));
+		andclauses = lappend(andclauses, andclause);
 	}
-	else if (not_clause((Node *) qual))
-		return (List *) make_notclause((Expr *) qual_cleanup((Expr *) get_notclausearg(qual)));
-	else
-		return (List *) qual;
+
+	/* pull_ands is needed in case any sub-or_normalize succeeded */
+	return make_andclause(pull_ands(andclauses));
 }
 
-/*--------------------
- * flatten_andors
- *	  Given a qualification, simplify nested AND/OR clauses into flat
- *	  AND/OR clauses with more arguments.
- *
- * The parser regards AND and OR as purely binary operators, so a qual like
- *		(A = 1) OR (A = 2) OR (A = 3) ...
- * will produce a nested parsetree
- *		(OR (A = 1) (OR (A = 2) (OR (A = 3) ...)))
- * In reality, the optimizer and executor regard AND and OR as n-argument
- * operators, so this tree can be flattened to
- *		(OR (A = 1) (A = 2) (A = 3) ...)
- * which is the responsibility of this routine.
+/*
+ * find_ands
+ *	  Given a qualification tree with the 'not's pushed down, convert it
+ *	  to a tree in DNF by repeatedly applying the rule:
+ *				("AND" A ("OR" B C))  => ("OR" ("AND" A B) ("AND" A C))
  *
- * If 'deep' is true, we search the whole tree for AND/ORs to simplify;
- * if not, we consider only the top-level AND/OR/NOT structure.
+ *	  Note that 'and' clauses will always be turned into 'or' clauses
+ *	  if they contain any 'or' subclauses.
  *
- * Returns the rebuilt expr (note original list structure is not touched).
- *--------------------
+ * Returns the modified qualification.  AND/OR flatness is preserved.
  */
 static Expr *
-flatten_andors(Expr *qual, bool deep)
+find_ands(Expr *qual)
 {
 	if (qual == NULL)
 		return NULL;
 
-	if (and_clause((Node *) qual))
+	/* We used to recurse into opclauses here, but I see no reason to... */
+	if (or_clause((Node *) qual))
 	{
-		List	   *out_list = NIL;
-		List	   *arg;
-
-		foreach(arg, qual->args)
-		{
-			Expr   *subexpr = flatten_andors((Expr *) lfirst(arg), deep);
+		List	   *orlist = NIL;
+		List	   *temp;
 
-			/*
-			 * Note: we can destructively nconc the subexpression's arglist
-			 * because we know the recursive invocation of flatten_andors
-			 * will have built a new arglist not shared with any other expr.
-			 * Otherwise we'd need a listCopy here.
-			 */
-			if (and_clause((Node *) subexpr))
-				out_list = nconc(out_list, subexpr->args);
-			else
-				out_list = lappend(out_list, subexpr);
-		}
-		return make_andclause(out_list);
+		foreach(temp, qual->args)
+			orlist = lappend(orlist, find_ands(lfirst(temp)));
+		return make_orclause(pull_ors(orlist));
 	}
-	else if (or_clause((Node *) qual))
+	else if (and_clause((Node *) qual))
 	{
-		List	   *out_list = NIL;
-		List	   *arg;
-
-		foreach(arg, qual->args)
-		{
-			Expr   *subexpr = flatten_andors((Expr *) lfirst(arg), deep);
+		List	   *andlist = NIL;
+		List	   *temp;
 
-			/*
-			 * Note: we can destructively nconc the subexpression's arglist
-			 * because we know the recursive invocation of flatten_andors
-			 * will have built a new arglist not shared with any other expr.
-			 * Otherwise we'd need a listCopy here.
-			 */
-			if (or_clause((Node *) subexpr))
-				out_list = nconc(out_list, subexpr->args);
-			else
-				out_list = lappend(out_list, subexpr);
-		}
-		return make_orclause(out_list);
+		foreach(temp, qual->args)
+			andlist = lappend(andlist, find_ands(lfirst(temp)));
+		return and_normalize(pull_ands(andlist));
 	}
 	else if (not_clause((Node *) qual))
-		return make_notclause(flatten_andors(get_notclausearg(qual), deep));
-	else if (deep && is_opclause((Node *) qual))
-	{
-		Expr	   *left = (Expr *) get_leftop(qual);
-		Expr	   *right = (Expr *) get_rightop(qual);
-
-		if (right)
-			return make_clause(qual->opType, qual->oper,
-							   lcons(flatten_andors(left, deep),
-									 lcons(flatten_andors(right, deep),
-										   NIL)));
-		else
-			return make_clause(qual->opType, qual->oper,
-							   lcons(flatten_andors(left, deep),
-									 NIL));
-	}
+		return make_notclause(find_ands(get_notclausearg(qual)));
 	else
 		return qual;
 }
 
 /*
- * pull_ors
- *	  Pull the arguments of an 'or' clause nested within another 'or'
- *	  clause up into the argument list of the parent.
+ * and_normalize
+ *	  Given a list of exprs which are 'and'ed together, try to apply
+ *	  the distributive law
+ *				("AND" A ("OR" B C))  => ("OR" ("AND" A B) ("AND" A C))
+ *	  to convert the top-level AND clause to a top-level OR clause.
  *
- * Input is the arglist of an OR clause.
- * Returns the rebuilt arglist (note original list structure is not touched).
+ * Returns the resulting expression (could be an AND clause, an OR
+ * clause, or maybe even a single subexpression).
  */
-static List *
-pull_ors(List *orlist)
+static Expr *
+and_normalize(List *andlist)
 {
-	List	   *out_list = NIL;
-	List	   *arg;
-
-	foreach(arg, orlist)
-	{
-		Expr   *subexpr = (Expr *) lfirst(arg);
-
-		/*
-		 * Note: we can destructively nconc the subexpression's arglist
-		 * because we know the recursive invocation of pull_ors
-		 * will have built a new arglist not shared with any other expr.
-		 * Otherwise we'd need a listCopy here.
-		 */
-		if (or_clause((Node *) subexpr))
-			out_list = nconc(out_list, pull_ors(subexpr->args));
-		else
-			out_list = lappend(out_list, subexpr);
-	}
-	return out_list;
-}
+	Expr	   *distributable = NULL;
+	int			num_subclauses = 1;
+	List	   *orclauses = NIL;
+	List	   *temp;
 
-/*
- * pull_ands
- *	  Pull the arguments of an 'and' clause nested within another 'and'
- *	  clause up into the argument list of the parent.
- *
- * Returns the modified list.
- */
-static List *
-pull_ands(List *andlist)
-{
-	List	   *out_list = NIL;
-	List	   *arg;
+	if (andlist == NIL)
+		return NULL;			/* probably can't happen */
+	if (lnext(andlist) == NIL)
+		return lfirst(andlist);	/* single-expression AND (can this happen?) */
 
-	foreach(arg, andlist)
+	/*
+	 * If we have a choice of OR clauses, pick the one with the
+	 * most subclauses.  Because we initialized num_subclauses = 1,
+	 * any OR clauses with only one arg will be ignored as useless.
+	 */
+	foreach(temp, andlist)
 	{
-		Expr   *subexpr = (Expr *) lfirst(arg);
+		Expr   *clause = lfirst(temp);
 
-		/*
-		 * Note: we can destructively nconc the subexpression's arglist
-		 * because we know the recursive invocation of pull_ands
-		 * will have built a new arglist not shared with any other expr.
-		 * Otherwise we'd need a listCopy here.
-		 */
-		if (and_clause((Node *) subexpr))
-			out_list = nconc(out_list, pull_ands(subexpr->args));
-		else
-			out_list = lappend(out_list, subexpr);
-	}
-	return out_list;
-}
-
-/*
- * or_normalize
- *	  Given a list of exprs which are 'or'ed together, distribute any
- *	  'and' clauses.
- *
- * Returns the modified list.
- *
- */
-static List *
-or_normalize(List *orlist)
-{
-	List	   *distributable = NIL;
-	List	   *new_orlist = NIL;
-	List	   *temp = NIL;
-
-	if (orlist == NIL)
-		return NIL;
-
-	foreach(temp, orlist)
-	{
-		if (and_clause(lfirst(temp)))
+		if (or_clause((Node *) clause))
 		{
-			distributable = lfirst(temp);
-			break;
-		}
-	}
-	if (distributable)
-		new_orlist = LispRemove(distributable, orlist);
+			int		nclauses = length(clause->args);
 
-	if (new_orlist)
-	{
-		return or_normalize(lcons(distribute_args(lfirst(new_orlist),
-										((Expr *) distributable)->args),
-								  lnext(new_orlist)));
+			if (nclauses > num_subclauses)
+			{
+				distributable = clause;
+				num_subclauses = nclauses;
+			}
+		}
 	}
-	else
-		return orlist;
-}
 
-/*
- * distribute_args
- *	  Create new 'or' clauses by or'ing 'item' with each element of 'args'.
- *	  E.g.: (distribute-args A ("AND" B C)) => ("AND" ("OR" A B) ("OR" A C))
- *
- * Returns an 'and' clause.
- *
- */
-static List *
-distribute_args(List *item, List *args)
-{
-	List	   *t_list = NIL;
-	List	   *temp;
+	/* if there's no suitable OR clause, we can't transform the AND */
+	if (! distributable)
+		return make_andclause(andlist);
 
-	if (args == NULL)
-		return item;
+	/* Caution: lremove destructively modifies the input andlist.
+	 * This should be OK, since and_normalize is only called with
+	 * freshly constructed lists that are not referenced elsewhere.
+	 */
+	andlist = lremove(distributable, andlist);
 
-	foreach(temp, args)
+	foreach(temp, distributable->args)
 	{
-		List	   *n_list;
+		Expr	   *orclause = lfirst(temp);
 
-		n_list = or_normalize(pull_ors(lcons(item,
-											 lcons(lfirst(temp),
-												   NIL))));
-		t_list = lappend(t_list, make_orclause(n_list));
+		/* pull_ands is needed here in case orclause has a top-level AND.
+		 * Then we recursively apply and_normalize, since there might
+		 * be an OR subclause in the resulting AND-list.
+		 * Note: we rely on pull_ands to build a fresh list,
+		 * and not damage the given andlist.
+		 */
+		orclause = and_normalize(pull_ands(lcons(orclause, andlist)));
+		orclauses = lappend(orclauses, orclause);
 	}
-	return (List *) make_andclause(t_list);
-}
-
-/*
- * remove_duplicates
- */
-static List *
-remove_duplicates(List *list)
-{
-	List	   *result = NIL;
-	List	   *i;
 
-	if (length(list) == 1)
-		return list;
-
-	foreach(i, list)
-	{
-		if (! member(lfirst(i), result))
-			result = lappend(result, lfirst(i));
-	}
-	return result;
+	/* pull_ors is needed in case any sub-and_normalize succeeded */
+	return make_orclause(pull_ors(orclauses));
 }
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index 8e4128b8c60..161419f29d8 100644
--- a/src/include/optimizer/prep.h
+++ b/src/include/optimizer/prep.h
@@ -1,12 +1,12 @@
 /*-------------------------------------------------------------------------
  *
  * prep.h
- *	  prototypes for files in prep.c
+ *	  prototypes for files in optimizer/prep/
  *
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: prep.h,v 1.17 1999/07/16 17:07:34 momjian Exp $
+ * $Id: prep.h,v 1.18 1999/09/12 18:08:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -20,6 +20,7 @@
  * prototypes for prepqual.c
  */
 extern List *cnfify(Expr *qual, bool removeAndFlag);
+extern Expr *dnfify(Expr *qual);
 
 /*
  * prototypes for preptlist.c
-- 
GitLab