Skip to content
Snippets Groups Projects
Commit 2119cc06 authored by Tom Lane's avatar Tom Lane
Browse files

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.
parent 4644fc80
No related branches found
No related tags found
No related merge requests found
/*------------------------------------------------------------------------- /*-------------------------------------------------------------------------
* *
* prepqual.c * prepqual.c
* Routines for preprocessing the parse tree qualification * Routines for preprocessing qualification expressions
* *
* Copyright (c) 1994, Regents of the University of California * Copyright (c) 1994, Regents of the University of California
* *
* *
* IDENTIFICATION * 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 @@ ...@@ -20,28 +20,33 @@
#include "optimizer/prep.h" #include "optimizer/prep.h"
#include "utils/lsyscache.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_ors(List *orlist);
static List *pull_ands(List *andlist); static List *pull_ands(List *andlist);
static Expr *find_nots(Expr *qual); static Expr *find_nots(Expr *qual);
static Expr *push_nots(Expr *qual); static Expr *push_nots(Expr *qual);
static Expr *normalize(Expr *qual); static Expr *find_ors(Expr *qual);
static List *or_normalize(List *orlist); static Expr *or_normalize(List *orlist);
static List *distribute_args(List *item, List *args); static Expr *find_ands(Expr *qual);
static List *qual_cleanup(Expr *qual); static Expr *and_normalize(List *andlist);
static List *remove_duplicates(List *list);
/***************************************************************************** /*****************************************************************************
* *
* CNF CONVERSION ROUTINES * CNF/DNF CONVERSION ROUTINES
* *
* NOTES: * These routines convert an arbitrary boolean expression into
* The basic algorithms for normalizing the qualification are taken * conjunctive normal form or disjunctive normal form.
* from ingres/source/qrymod/norml.c
* *
* Remember that the initial qualification may consist of ARBITRARY * The result of these routines differs from a "true" CNF/DNF in that
* combinations of clauses. In addition, before this routine is called, * we do not bother to detect common subexpressions; e.g., ("AND" A A)
* the qualification will contain explicit "AND"s. * 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); ...@@ -54,44 +59,225 @@ static List *remove_duplicates(List *list);
* Returns the modified qualification. * Returns the modified qualification.
* *
* If 'removeAndFlag' is true then it removes explicit AND at the top level, * If 'removeAndFlag' is true then it removes explicit AND at the top level,
* producing a list of implicitly-ANDed conditions. Otherwise, a normal * producing a list of implicitly-ANDed conditions. Otherwise, a regular
* boolean expression is returned. * boolean expression is returned. Since most callers pass 'true', we
* * prefer to declare the result as List *, not Expr *.
* NOTE: this routine is called by the planner (removeAndFlag = true)
* and from the rule manager (removeAndFlag = false).
*
*/ */
List * List *
cnfify(Expr *qual, bool removeAndFlag) cnfify(Expr *qual, bool removeAndFlag)
{ {
Expr *newqual = NULL; Expr *newqual;
if (qual == NULL)
return NIL;
if (qual != NULL)
{
/* Flatten AND and OR groups throughout the tree. /* Flatten AND and OR groups throughout the tree.
* This improvement is always worthwhile. * This improvement is always worthwhile.
*/ */
newqual = flatten_andors(qual, true); newqual = flatten_andors(qual);
/* Push down NOTs. We do this only in the top-level boolean /* Push down NOTs. We do this only in the top-level boolean
* expression, without examining arguments of operators/functions. * expression, without examining arguments of operators/functions.
*/ */
newqual = find_nots(newqual); newqual = find_nots(newqual);
/* Pushing NOTs could have brought AND/ORs together, so do /* Normalize into conjunctive normal form. */
* another flatten_andors (only in the top level); then normalize. newqual = find_ors(newqual);
*/
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);
if (removeAndFlag) if (removeAndFlag)
{ {
newqual = (Expr *) make_ands_implicit(newqual); 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);
return newqual;
} }
return (List *) (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)
{
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;
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) ...@@ -100,8 +286,7 @@ cnfify(Expr *qual, bool removeAndFlag)
* For 'NOT' clauses, apply push_not() to try to push down the 'NOT'. * For 'NOT' clauses, apply push_not() to try to push down the 'NOT'.
* For all other clause types, simply recurse. * For all other clause types, simply recurse.
* *
* Returns the modified qualification. * Returns the modified qualification. AND/OR flatness is preserved.
*
*/ */
static Expr * static Expr *
find_nots(Expr *qual) find_nots(Expr *qual)
...@@ -134,7 +319,7 @@ find_nots(Expr *qual) ...@@ -134,7 +319,7 @@ find_nots(Expr *qual)
foreach(temp, qual->args) foreach(temp, qual->args)
t_list = lappend(t_list, find_nots(lfirst(temp))); 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)) else if (or_clause((Node *) qual))
{ {
...@@ -143,7 +328,7 @@ find_nots(Expr *qual) ...@@ -143,7 +328,7 @@ find_nots(Expr *qual)
foreach(temp, qual->args) foreach(temp, qual->args)
t_list = lappend(t_list, find_nots(lfirst(temp))); 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)) else if (not_clause((Node *) qual))
return push_nots(get_notclausearg(qual)); return push_nots(get_notclausearg(qual));
...@@ -187,17 +372,19 @@ push_nots(Expr *qual) ...@@ -187,17 +372,19 @@ push_nots(Expr *qual)
} }
else if (and_clause((Node *) qual)) else if (and_clause((Node *) qual))
{ {
/* /*--------------------
* Apply DeMorgan's Laws: ("NOT" ("AND" A B)) => ("OR" ("NOT" A) * Apply DeMorgan's Laws:
* ("NOT" B)) ("NOT" ("OR" A B)) => ("AND" ("NOT" A) ("NOT" B)) * ("NOT" ("AND" A B)) => ("OR" ("NOT" A) ("NOT" B))
* i.e., continue negating down through the clause's descendants. * ("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 *t_list = NIL;
List *temp; List *temp;
foreach(temp, qual->args) foreach(temp, qual->args)
t_list = lappend(t_list, push_nots(lfirst(temp))); 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)) else if (or_clause((Node *) qual))
{ {
...@@ -206,7 +393,7 @@ push_nots(Expr *qual) ...@@ -206,7 +393,7 @@ push_nots(Expr *qual)
foreach(temp, qual->args) foreach(temp, qual->args)
t_list = lappend(t_list, push_nots(lfirst(temp))); 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)) else if (not_clause((Node *) qual))
{ {
...@@ -228,20 +415,18 @@ push_nots(Expr *qual) ...@@ -228,20 +415,18 @@ push_nots(Expr *qual)
} }
/* /*
* normalize * find_ors
* Given a qualification tree with the 'not's pushed down, convert it * Given a qualification tree with the 'not's pushed down, convert it
* to a tree in CNF by repeatedly applying the rule: * to a tree in CNF by repeatedly applying the rule:
* ("OR" A ("AND" B C)) => ("AND" ("OR" A B) ("OR" A C)) * ("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 * static Expr *
normalize(Expr *qual) find_ors(Expr *qual)
{ {
if (qual == NULL) if (qual == NULL)
return NULL; return NULL;
...@@ -249,346 +434,210 @@ normalize(Expr *qual) ...@@ -249,346 +434,210 @@ normalize(Expr *qual)
/* We used to recurse into opclauses here, but I see no reason to... */ /* We used to recurse into opclauses here, but I see no reason to... */
if (and_clause((Node *) qual)) if (and_clause((Node *) qual))
{ {
List *t_list = NIL; List *andlist = NIL;
List *temp; List *temp;
foreach(temp, qual->args) foreach(temp, qual->args)
t_list = lappend(t_list, normalize(lfirst(temp))); andlist = lappend(andlist, find_ors(lfirst(temp)));
return make_andclause(t_list); return make_andclause(pull_ands(andlist));
} }
else if (or_clause((Node *) qual)) else if (or_clause((Node *) qual))
{ {
/* XXX - let form, maybe incorrect */
List *orlist = NIL; List *orlist = NIL;
bool has_andclause = false;
List *temp; List *temp;
foreach(temp, qual->args) foreach(temp, qual->args)
orlist = lappend(orlist, normalize(lfirst(temp))); orlist = lappend(orlist, find_ors(lfirst(temp)));
foreach(temp, orlist) return or_normalize(pull_ors(orlist));
{
if (and_clause(lfirst(temp)))
{
has_andclause = true;
break;
}
}
if (has_andclause)
return make_andclause(or_normalize(orlist));
else
return make_orclause(orlist);
} }
else if (not_clause((Node *) qual)) else if (not_clause((Node *) qual))
return make_notclause(normalize(get_notclausearg(qual))); return make_notclause(find_ors(get_notclausearg(qual)));
else else
return qual; return qual;
} }
/* /*
* qual_cleanup * or_normalize
* Fix up a qualification by removing duplicate entries (left over from * Given a list of exprs which are 'or'ed together, try to apply
* normalization), and by removing 'and' and 'or' clauses which have only * the distributive law
* one remaining subexpr (e.g., ("AND" A) => A). * ("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 * static Expr *
qual_cleanup(Expr *qual) or_normalize(List *orlist)
{ {
if (qual == NULL) Expr *distributable = NULL;
return NIL; int num_subclauses = 1;
List *andclauses = NIL;
List *temp;
if (is_opclause((Node *) qual)) if (orlist == NIL)
{ return NULL; /* probably can't happen */
Expr *left = (Expr *) get_leftop(qual); if (lnext(orlist) == NIL)
Expr *right = (Expr *) get_rightop(qual); return lfirst(orlist); /* single-expression OR (can this happen?) */
if (right) /*
return (List *) make_clause(qual->opType, qual->oper, * If we have a choice of AND clauses, pick the one with the
lcons(qual_cleanup(left), * most subclauses. Because we initialized num_subclauses = 1,
lcons(qual_cleanup(right), * any AND clauses with only one arg will be ignored as useless.
NIL))); */
else foreach(temp, orlist)
return (List *) make_clause(qual->opType, qual->oper,
lcons(qual_cleanup(left),
NIL));
}
else if (and_clause((Node *) qual))
{ {
List *t_list = NIL; Expr *clause = lfirst(temp);
List *temp;
List *new_and_args;
foreach(temp, qual->args) if (and_clause((Node *) clause))
t_list = lappend(t_list, qual_cleanup(lfirst(temp))); {
int nclauses = length(clause->args);
new_and_args = remove_duplicates(t_list);
if (length(new_and_args) > 1) if (nclauses > num_subclauses)
return (List *) make_andclause(new_and_args);
else
return lfirst(new_and_args);
}
else if (or_clause((Node *) qual))
{ {
List *t_list = NIL; distributable = clause;
List *temp; num_subclauses = nclauses;
List *new_or_args; }
}
}
foreach(temp, qual->args) /* if there's no suitable AND clause, we can't transform the OR */
t_list = lappend(t_list, qual_cleanup(lfirst(temp))); 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) foreach(temp, distributable->args)
return (List *) make_orclause(new_or_args); {
else Expr *andclause = lfirst(temp);
return lfirst(new_or_args);
/* 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))); /* pull_ands is needed in case any sub-or_normalize succeeded */
else return make_andclause(pull_ands(andclauses));
return (List *) qual;
} }
/*-------------------- /*
* flatten_andors * find_ands
* Given a qualification, simplify nested AND/OR clauses into flat * Given a qualification tree with the 'not's pushed down, convert it
* AND/OR clauses with more arguments. * to a tree in DNF by repeatedly applying the rule:
* * ("AND" A ("OR" B C)) => ("OR" ("AND" A B) ("AND" A C))
* 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.
* *
* If 'deep' is true, we search the whole tree for AND/ORs to simplify; * Note that 'and' clauses will always be turned into 'or' clauses
* if not, we consider only the top-level AND/OR/NOT structure. * 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 * static Expr *
flatten_andors(Expr *qual, bool deep) find_ands(Expr *qual)
{ {
if (qual == NULL) if (qual == NULL)
return 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;
/* foreach(temp, qual->args)
* Note: we can destructively nconc the subexpression's arglist orlist = lappend(orlist, find_ands(lfirst(temp)));
* because we know the recursive invocation of flatten_andors return make_orclause(pull_ors(orlist));
* 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)) 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;
/* foreach(temp, qual->args)
* Note: we can destructively nconc the subexpression's arglist andlist = lappend(andlist, find_ands(lfirst(temp)));
* because we know the recursive invocation of flatten_andors return and_normalize(pull_ands(andlist));
* 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)) else if (not_clause((Node *) qual))
return make_notclause(flatten_andors(get_notclausearg(qual), deep)); return make_notclause(find_ands(get_notclausearg(qual)));
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));
}
else else
return qual; return qual;
} }
/* /*
* pull_ors * and_normalize
* Pull the arguments of an 'or' clause nested within another 'or' * Given a list of exprs which are 'and'ed together, try to apply
* clause up into the argument list of the parent. * 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 resulting expression (could be an AND clause, an OR
* Returns the rebuilt arglist (note original list structure is not touched). * clause, or maybe even a single subexpression).
*/ */
static List * static Expr *
pull_ors(List *orlist) and_normalize(List *andlist)
{
List *out_list = NIL;
List *arg;
foreach(arg, orlist)
{ {
Expr *subexpr = (Expr *) lfirst(arg); Expr *distributable = NULL;
int num_subclauses = 1;
List *orclauses = NIL;
List *temp;
/* if (andlist == NIL)
* Note: we can destructively nconc the subexpression's arglist return NULL; /* probably can't happen */
* because we know the recursive invocation of pull_ors if (lnext(andlist) == NIL)
* will have built a new arglist not shared with any other expr. return lfirst(andlist); /* single-expression AND (can this happen?) */
* 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 * If we have a choice of OR clauses, pick the one with the
* Pull the arguments of an 'and' clause nested within another 'and' * most subclauses. Because we initialized num_subclauses = 1,
* clause up into the argument list of the parent. * any OR clauses with only one arg will be ignored as useless.
*
* Returns the modified list.
*/ */
static List * foreach(temp, andlist)
pull_ands(List *andlist)
{ {
List *out_list = NIL; Expr *clause = lfirst(temp);
List *arg;
foreach(arg, andlist) if (or_clause((Node *) clause))
{ {
Expr *subexpr = (Expr *) lfirst(arg); int nclauses = length(clause->args);
/* if (nclauses > num_subclauses)
* 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))) distributable = clause;
{ num_subclauses = nclauses;
distributable = lfirst(temp);
break;
}
} }
if (distributable)
new_orlist = LispRemove(distributable, orlist);
if (new_orlist)
{
return or_normalize(lcons(distribute_args(lfirst(new_orlist),
((Expr *) distributable)->args),
lnext(new_orlist)));
} }
else
return orlist;
} }
/* /* if there's no suitable OR clause, we can't transform the AND */
* distribute_args if (! distributable)
* Create new 'or' clauses by or'ing 'item' with each element of 'args'. return make_andclause(andlist);
* 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 (args == NULL) /* Caution: lremove destructively modifies the input andlist.
return item; * 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));
}
return (List *) make_andclause(t_list);
}
/* /* pull_ands is needed here in case orclause has a top-level AND.
* remove_duplicates * 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.
*/ */
static List * orclause = and_normalize(pull_ands(lcons(orclause, andlist)));
remove_duplicates(List *list) orclauses = lappend(orclauses, orclause);
{
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));
} }
/*------------------------------------------------------------------------- /*-------------------------------------------------------------------------
* *
* prep.h * prep.h
* prototypes for files in prep.c * prototypes for files in optimizer/prep/
* *
* *
* Copyright (c) 1994, Regents of the University of California * 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 @@ ...@@ -20,6 +20,7 @@
* prototypes for prepqual.c * prototypes for prepqual.c
*/ */
extern List *cnfify(Expr *qual, bool removeAndFlag); extern List *cnfify(Expr *qual, bool removeAndFlag);
extern Expr *dnfify(Expr *qual);
/* /*
* prototypes for preptlist.c * prototypes for preptlist.c
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment