Commit 2119cc06 authored by Tom Lane's avatar Tom Lane

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
/*------------------------------------------------------------------------- /*-------------------------------------------------------------------------
* *
* 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) 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. newqual = (Expr *) make_ands_implicit(newqual);
* This improvement is always worthwhile. }
*/
newqual = flatten_andors(qual, true); return (List *) newqual;
/* Push down NOTs. We do this only in the top-level boolean }
* expression, without examining arguments of operators/functions.
*/ /*
newqual = find_nots(newqual); * dnfify
/* Pushing NOTs could have brought AND/ORs together, so do * Convert a qualification to disjunctive normal form by applying
* another flatten_andors (only in the top level); then normalize. * successive normalizations.
*/ *
newqual = normalize(flatten_andors(newqual, false)); * Returns the modified qualification.
/* Do we need a flatten here? Anyway, clean up after normalize. */ *
newqual = (Expr *) qual_cleanup(flatten_andors(newqual, false)); * We do not offer a 'removeOrFlag' in this case; the usages are
/* This flatten is almost surely a waste of time... */ * different.
newqual = flatten_andors(newqual, false); */
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) ...@@ -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)
t_list = lappend(t_list, qual_cleanup(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) if (nclauses > num_subclauses)
return (List *) make_andclause(new_and_args); {
else distributable = clause;
return lfirst(new_and_args); num_subclauses = nclauses;
}
}
} }
else if (or_clause((Node *) qual))
{
List *t_list = NIL;
List *temp;
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 *orlist = NIL;
List *arg; List *temp;
foreach(arg, qual->args)
{
Expr *subexpr = flatten_andors((Expr *) lfirst(arg), deep);
/* 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 *andlist = NIL;
List *arg; List *temp;
foreach(arg, qual->args)
{
Expr *subexpr = flatten_andors((Expr *) lfirst(arg), deep);
/* 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; Expr *distributable = NULL;
List *arg; int num_subclauses = 1;
List *orclauses = NIL;
foreach(arg, orlist) List *temp;
{
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;
}
/* if (andlist == NIL)
* pull_ands return NULL; /* probably can't happen */
* Pull the arguments of an 'and' clause nested within another 'and' if (lnext(andlist) == NIL)
* clause up into the argument list of the parent. return lfirst(andlist); /* single-expression AND (can this happen?) */
*
* Returns the modified list.
*/
static List *
pull_ands(List *andlist)
{
List *out_list = NIL;
List *arg;
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);
/* if (or_clause((Node *) clause))
* 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 = lfirst(temp); int nclauses = length(clause->args);
break;
}
}
if (distributable)
new_orlist = LispRemove(distributable, orlist);
if (new_orlist) if (nclauses > num_subclauses)
{ {
return or_normalize(lcons(distribute_args(lfirst(new_orlist), distributable = clause;
((Expr *) distributable)->args), num_subclauses = nclauses;
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, /* pull_ands is needed here in case orclause has a top-level AND.
lcons(lfirst(temp), * Then we recursively apply and_normalize, since there might
NIL)))); * be an OR subclause in the resulting AND-list.
t_list = lappend(t_list, make_orclause(n_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) /* pull_ors is needed in case any sub-and_normalize succeeded */
return list; return make_orclause(pull_ors(orclauses));
foreach(i, list)
{
if (! member(lfirst(i), result))
result = lappend(result, lfirst(i));
}
return result;
} }
/*------------------------------------------------------------------------- /*-------------------------------------------------------------------------
* *
* 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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment