diff --git a/src/backend/optimizer/prep/prepqual.c b/src/backend/optimizer/prep/prepqual.c index 990a6439684d6b399a4f8d6c9066b7afabf99eec..974884d8bcc6ca04290bf7432e78fcd624743836 100644 --- a/src/backend/optimizer/prep/prepqual.c +++ b/src/backend/optimizer/prep/prepqual.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepqual.c,v 1.17 1999/07/16 04:59:21 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepqual.c,v 1.18 1999/09/07 03:47:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -15,14 +15,12 @@ #include "postgres.h" - - #include "nodes/makefuncs.h" #include "optimizer/clauses.h" #include "optimizer/prep.h" #include "utils/lsyscache.h" -static Expr *pull_args(Expr *qual); +static Expr *flatten_andors(Expr *qual, bool deep); static List *pull_ors(List *orlist); static List *pull_ands(List *andlist); static Expr *find_nots(Expr *qual); @@ -31,7 +29,6 @@ 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_ands(Expr *qual); static List *remove_duplicates(List *list); /***************************************************************************** @@ -54,9 +51,11 @@ static List *remove_duplicates(List *list); * Convert a qualification to conjunctive normal form by applying * successive normalizations. * - * Returns the modified qualification with an extra level of nesting. + * Returns the modified qualification. * - * If 'removeAndFlag' is true then it removes the explicit ANDs. + * 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). @@ -69,17 +68,26 @@ cnfify(Expr *qual, bool removeAndFlag) if (qual != NULL) { - newqual = find_nots(pull_args(qual)); - newqual = normalize(pull_args(newqual)); - newqual = (Expr *) qual_cleanup(pull_args(newqual)); - newqual = pull_args(newqual);; + /* 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); if (removeAndFlag) { - if (and_clause((Node *) newqual)) - newqual = (Expr *) remove_ands(newqual); - else - newqual = (Expr *) remove_ands(make_andclause(lcons(newqual, NIL))); + newqual = (Expr *) make_ands_implicit(newqual); } } @@ -88,9 +96,8 @@ cnfify(Expr *qual, bool removeAndFlag) /* * find_nots - * Traverse the qualification, looking for 'not's to take care of. - * For 'not' clauses, remove the 'not' and push it down to the clauses' - * descendants. + * Traverse the qualification, looking for 'NOT's to take care of. + * For 'NOT' clauses, apply push_not() to try to push down the 'NOT'. * For all other clause types, simply recurse. * * Returns the modified qualification. @@ -102,6 +109,8 @@ find_nots(Expr *qual) if (qual == NULL) return NULL; +#ifdef NOT_USED + /* recursing into operator expressions is probably not worth it. */ if (is_opclause((Node *) qual)) { Expr *left = (Expr *) get_leftop(qual); @@ -117,20 +126,20 @@ find_nots(Expr *qual) lcons(find_nots(left), NIL)); } - else if (and_clause((Node *) qual)) +#endif + if (and_clause((Node *) qual)) { - List *temp = NIL; List *t_list = NIL; + List *temp; foreach(temp, qual->args) t_list = lappend(t_list, find_nots(lfirst(temp))); - return make_andclause(t_list); } else if (or_clause((Node *) qual)) { - List *temp = NIL; List *t_list = NIL; + List *temp; foreach(temp, qual->args) t_list = lappend(t_list, find_nots(lfirst(temp))); @@ -142,13 +151,91 @@ find_nots(Expr *qual) return qual; } +/* + * push_nots + * Push down a 'NOT' as far as possible. + * + * Input is an expression to be negated (e.g., the argument of a NOT clause). + * Returns a new qual equivalent to the negation of the given qual. + */ +static Expr * +push_nots(Expr *qual) +{ + if (qual == NULL) + return make_notclause(qual); /* XXX is this right? Or possible? */ + + /* + * Negate an operator clause if possible: ("NOT" (< A B)) => (> A B) + * Otherwise, retain the clause as it is (the 'not' can't be pushed + * down any farther). + */ + if (is_opclause((Node *) qual)) + { + Oper *oper = (Oper *) ((Expr *) qual)->oper; + Oid negator = get_negator(oper->opno); + + if (negator) + { + Oper *op = (Oper *) makeOper(negator, + InvalidOid, + oper->opresulttype, + 0, NULL); + return make_opclause(op, get_leftop(qual), get_rightop(qual)); + } + else + return make_notclause(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. + */ + List *t_list = NIL; + List *temp; + + foreach(temp, qual->args) + t_list = lappend(t_list, push_nots(lfirst(temp))); + return make_orclause(t_list); + } + else if (or_clause((Node *) qual)) + { + List *t_list = NIL; + List *temp; + + foreach(temp, qual->args) + t_list = lappend(t_list, push_nots(lfirst(temp))); + return make_andclause(t_list); + } + else if (not_clause((Node *) qual)) + { + /* + * Another 'not' cancels this 'not', so eliminate the 'not' and + * stop negating this branch. But search the subexpression for + * more 'not's to simplify. + */ + return find_nots(get_notclausearg(qual)); + } + else + { + /* + * We don't know how to negate anything else, place a 'not' at + * this level. + */ + return make_notclause(qual); + } +} + /* * normalize * 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. + * 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. * @@ -159,25 +246,11 @@ normalize(Expr *qual) if (qual == NULL) return NULL; - 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(normalize(left), - lcons(normalize(right), - NIL))); - else - return make_clause(qual->opType, qual->oper, - lcons(normalize(left), - NIL)); - } - else if (and_clause((Node *) qual)) + /* We used to recurse into opclauses here, but I see no reason to... */ + if (and_clause((Node *) qual)) { - List *temp = NIL; List *t_list = NIL; + List *temp; foreach(temp, qual->args) t_list = lappend(t_list, normalize(lfirst(temp))); @@ -187,8 +260,8 @@ normalize(Expr *qual) { /* XXX - let form, maybe incorrect */ List *orlist = NIL; - List *temp = NIL; - bool has_andclause = FALSE; + bool has_andclause = false; + List *temp; foreach(temp, qual->args) orlist = lappend(orlist, normalize(lfirst(temp))); @@ -196,15 +269,14 @@ normalize(Expr *qual) { if (and_clause(lfirst(temp))) { - has_andclause = TRUE; + has_andclause = true; break; } } - if (has_andclause == TRUE) + if (has_andclause) return make_andclause(or_normalize(orlist)); else return make_orclause(orlist); - } else if (not_clause((Node *) qual)) return make_notclause(normalize(get_notclausearg(qual))); @@ -216,10 +288,9 @@ normalize(Expr *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 valid expr (e.g., ("AND" A) => A). - * - * Returns the modified qualfication. + * one remaining subexpr (e.g., ("AND" A) => A). * + * Returns the modified qualification. */ static List * qual_cleanup(Expr *qual) @@ -244,9 +315,9 @@ qual_cleanup(Expr *qual) } else if (and_clause((Node *) qual)) { - List *temp = NIL; List *t_list = NIL; - List *new_and_args = NIL; + List *temp; + List *new_and_args; foreach(temp, qual->args) t_list = lappend(t_list, qual_cleanup(lfirst(temp))); @@ -260,16 +331,15 @@ qual_cleanup(Expr *qual) } else if (or_clause((Node *) qual)) { - List *temp = NIL; List *t_list = NIL; - List *new_or_args = NIL; + List *temp; + List *new_or_args; foreach(temp, qual->args) t_list = lappend(t_list, qual_cleanup(lfirst(temp))); new_or_args = remove_duplicates(t_list); - if (length(new_or_args) > 1) return (List *) make_orclause(new_or_args); else @@ -281,54 +351,93 @@ qual_cleanup(Expr *qual) return (List *) qual; } -/* - * pull_args - * Given a qualification, eliminate nested 'and' and 'or' clauses. +/*-------------------- + * flatten_andors + * Given a qualification, simplify nested AND/OR clauses into flat + * AND/OR clauses with more arguments. * - * Returns the modified qualification. + * 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; + * if not, we consider only the top-level AND/OR/NOT structure. * + * Returns the rebuilt expr (note original list structure is not touched). + *-------------------- */ static Expr * -pull_args(Expr *qual) +flatten_andors(Expr *qual, bool deep) { if (qual == NULL) return NULL; - if (is_opclause((Node *) qual)) + if (and_clause((Node *) qual)) + { + List *out_list = NIL; + List *arg; + + foreach(arg, qual->args) + { + Expr *subexpr = flatten_andors((Expr *) lfirst(arg), deep); + + /* + * 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), deep); + + /* + * 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), 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(pull_args(left), - lcons(pull_args(right), + lcons(flatten_andors(left, deep), + lcons(flatten_andors(right, deep), NIL))); else return make_clause(qual->opType, qual->oper, - lcons(pull_args(left), + lcons(flatten_andors(left, deep), NIL)); } - else if (and_clause((Node *) qual)) - { - List *temp = NIL; - List *t_list = NIL; - - foreach(temp, qual->args) - t_list = lappend(t_list, pull_args(lfirst(temp))); - return make_andclause(pull_ands(t_list)); - } - else if (or_clause((Node *) qual)) - { - List *temp = NIL; - List *t_list = NIL; - - foreach(temp, qual->args) - t_list = lappend(t_list, pull_args(lfirst(temp))); - return make_orclause(pull_ors(t_list)); - } - else if (not_clause((Node *) qual)) - return make_notclause(pull_args(get_notclausearg(qual))); else return qual; } @@ -338,23 +447,31 @@ pull_args(Expr *qual) * Pull the arguments of an 'or' clause nested within another 'or' * clause up into the argument list of the parent. * - * Returns the modified list. + * 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) { - if (orlist == NIL) - return NIL; + List *out_list = NIL; + List *arg; - if (or_clause(lfirst(orlist))) + foreach(arg, orlist) { - List *args = ((Expr *) lfirst(orlist))->args; + Expr *subexpr = (Expr *) lfirst(arg); - return (pull_ors(nconc(copyObject((Node *) args), - copyObject((Node *) lnext(orlist))))); + /* + * 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); } - else - return lcons(lfirst(orlist), pull_ors(lnext(orlist))); + return out_list; } /* @@ -367,94 +484,25 @@ pull_ors(List *orlist) static List * pull_ands(List *andlist) { - if (andlist == NIL) - return NIL; + List *out_list = NIL; + List *arg; - if (and_clause(lfirst(andlist))) - { - List *args = ((Expr *) lfirst(andlist))->args; - - return (pull_ands(nconc(copyObject((Node *) args), - copyObject((Node *) lnext(andlist))))); - } - else - return lcons(lfirst(andlist), pull_ands(lnext(andlist))); -} - -/* - * push_nots - * Negate the descendants of a 'not' clause. - * - * Returns the modified qualification. - * - */ -static Expr * -push_nots(Expr *qual) -{ - if (qual == NULL) - return NULL; - - /* - * Negate an operator clause if possible: ("NOT" (< A B)) => (> A B) - * Otherwise, retain the clause as it is (the 'not' can't be pushed - * down any farther). - */ - if (is_opclause((Node *) qual)) - { - Oper *oper = (Oper *) ((Expr *) qual)->oper; - Oid negator = get_negator(oper->opno); - - if (negator) - { - Oper *op = (Oper *) makeOper(negator, - InvalidOid, - oper->opresulttype, - 0, NULL); - - op->op_fcache = (FunctionCache *) NULL; - return make_opclause(op, get_leftop(qual), get_rightop(qual)); - } - else - return make_notclause(qual); - } - else if (and_clause((Node *) qual)) + foreach(arg, andlist) { + Expr *subexpr = (Expr *) lfirst(arg); /* - * 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. + * 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. */ - List *temp = NIL; - List *t_list = NIL; - - foreach(temp, qual->args) - t_list = lappend(t_list, push_nots(lfirst(temp))); - return make_orclause(t_list); - } - else if (or_clause((Node *) qual)) - { - List *temp = NIL; - List *t_list = NIL; - - foreach(temp, qual->args) - t_list = lappend(t_list, push_nots(lfirst(temp))); - return make_andclause(t_list); + if (and_clause((Node *) subexpr)) + out_list = nconc(out_list, pull_ands(subexpr->args)); + else + out_list = lappend(out_list, subexpr); } - else if (not_clause((Node *) qual)) - - /* - * Another 'not' cancels this 'not', so eliminate the 'not' and - * stop negating this branch. - */ - return find_nots(get_notclausearg(qual)); - else - - /* - * We don't know how to negate anything else, place a 'not' at - * this level. - */ - return make_notclause(qual); + return out_list; } /* @@ -478,16 +526,19 @@ or_normalize(List *orlist) foreach(temp, orlist) { if (and_clause(lfirst(temp))) + { 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)))); + return or_normalize(lcons(distribute_args(lfirst(new_orlist), + ((Expr *) distributable)->args), + lnext(new_orlist))); } else return orlist; @@ -504,104 +555,40 @@ or_normalize(List *orlist) static List * distribute_args(List *item, List *args) { - List *or_list = NIL; - List *n_list = NIL; - List *temp = NIL; List *t_list = NIL; + List *temp; if (args == NULL) return item; foreach(temp, args) { + List *n_list; + n_list = or_normalize(pull_ors(lcons(item, - lcons(lfirst(temp), NIL)))); - or_list = (List *) make_orclause(n_list); - t_list = lappend(t_list, or_list); + lcons(lfirst(temp), + NIL)))); + t_list = lappend(t_list, make_orclause(n_list)); } return (List *) make_andclause(t_list); } -/* - * remove_ands - * Remove the explicit "AND"s from the qualification: - * ("AND" A B) => (A B) - * - * RETURNS : qual - * MODIFIES: qual - */ -static List * -remove_ands(Expr *qual) -{ - List *t_list = NIL; - - if (qual == NULL) - return NIL; - if (is_opclause((Node *) qual)) - { - Expr *left = (Expr *) get_leftop(qual); - Expr *right = (Expr *) get_rightop(qual); - - if (right) - return (List *) make_clause(qual->opType, qual->oper, - lcons(remove_ands(left), - lcons(remove_ands(right), - NIL))); - else - return (List *) make_clause(qual->opType, qual->oper, - lcons(remove_ands(left), - NIL)); - } - else if (and_clause((Node *) qual)) - { - List *temp = NIL; - - foreach(temp, qual->args) - t_list = lappend(t_list, remove_ands(lfirst(temp))); - return t_list; - } - else if (or_clause((Node *) qual)) - { - List *temp = NIL; - - foreach(temp, qual->args) - t_list = lappend(t_list, remove_ands(lfirst(temp))); - return (List *) make_orclause((List *) t_list); - } - else if (not_clause((Node *) qual)) - return (List *) make_notclause((Expr *) remove_ands((Expr *) get_notclausearg(qual))); - else - return (List *) qual; -} - /* * remove_duplicates */ static List * remove_duplicates(List *list) { - List *i; - List *j; List *result = NIL; - bool there_exists_duplicate = false; + List *i; if (length(list) == 1) return list; foreach(i, list) { - if (i != NIL) - { - foreach(j, lnext(i)) - { - if (equal(lfirst(i), lfirst(j))) - there_exists_duplicate = true; - } - if (!there_exists_duplicate) - result = lappend(result, lfirst(i)); - - there_exists_duplicate = false; - } + if (! member(lfirst(i), result)) + result = lappend(result, lfirst(i)); } return result; }