diff --git a/src/backend/optimizer/prep/prepqual.c b/src/backend/optimizer/prep/prepqual.c index bda23937da8..a701390afe7 100644 --- a/src/backend/optimizer/prep/prepqual.c +++ b/src/backend/optimizer/prep/prepqual.c @@ -32,6 +32,7 @@ #include "postgres.h" +#include "nodes/makefuncs.h" #include "optimizer/clauses.h" #include "optimizer/prep.h" #include "utils/lsyscache.h" @@ -39,12 +40,226 @@ static List *pull_ands(List *andlist); static List *pull_ors(List *orlist); -static Expr *find_nots(Expr *qual); -static Expr *push_nots(Expr *qual); static Expr *find_duplicate_ors(Expr *qual); static Expr *process_duplicate_ors(List *orlist); +/* + * negate_clause + * Negate a Boolean expression. + * + * Input is a clause to be negated (e.g., the argument of a NOT clause). + * Returns a new clause equivalent to the negation of the given clause. + * + * Although this can be invoked on its own, it's mainly intended as a helper + * for eval_const_expressions(), and that context drives several design + * decisions. In particular, if the input is already AND/OR flat, we must + * preserve that property. We also don't bother to recurse in situations + * where we can assume that lower-level executions of eval_const_expressions + * would already have simplified sub-clauses of the input. + * + * The difference between this and a simple make_notclause() is that this + * tries to get rid of the NOT node by logical simplification. It's clearly + * always a win if the NOT node can be eliminated altogether. However, our + * use of DeMorgan's laws could result in having more NOT nodes rather than + * fewer. We do that unconditionally anyway, because in WHERE clauses it's + * important to expose as much top-level AND/OR structure as possible. + * Also, eliminating an intermediate NOT may allow us to flatten two levels + * of AND or OR together that we couldn't have otherwise. Finally, one of + * the motivations for doing this is to ensure that logically equivalent + * expressions will be seen as physically equal(), so we should always apply + * the same transformations. + */ +Node * +negate_clause(Node *node) +{ + if (node == NULL) /* should not happen */ + elog(ERROR, "can't negate an empty subexpression"); + switch (nodeTag(node)) + { + case T_Const: + { + Const *c = (Const *) node; + + /* NOT NULL is still NULL */ + if (c->constisnull) + return makeBoolConst(false, true); + /* otherwise pretty easy */ + return makeBoolConst(!DatumGetBool(c->constvalue), false); + } + break; + case T_OpExpr: + { + /* + * Negate operator if possible: (NOT (< A B)) => (>= A B) + */ + OpExpr *opexpr = (OpExpr *) node; + Oid negator = get_negator(opexpr->opno); + + if (negator) + { + OpExpr *newopexpr = makeNode(OpExpr); + + newopexpr->opno = negator; + newopexpr->opfuncid = InvalidOid; + newopexpr->opresulttype = opexpr->opresulttype; + newopexpr->opretset = opexpr->opretset; + newopexpr->args = opexpr->args; + newopexpr->location = opexpr->location; + return (Node *) newopexpr; + } + } + break; + case T_ScalarArrayOpExpr: + { + /* + * Negate a ScalarArrayOpExpr if its operator has a negator; + * for example x = ANY (list) becomes x <> ALL (list) + */ + ScalarArrayOpExpr *saopexpr = (ScalarArrayOpExpr *) node; + Oid negator = get_negator(saopexpr->opno); + + if (negator) + { + ScalarArrayOpExpr *newopexpr = makeNode(ScalarArrayOpExpr); + + newopexpr->opno = negator; + newopexpr->opfuncid = InvalidOid; + newopexpr->useOr = !saopexpr->useOr; + newopexpr->args = saopexpr->args; + newopexpr->location = saopexpr->location; + return (Node *) newopexpr; + } + } + break; + case T_BoolExpr: + { + BoolExpr *expr = (BoolExpr *) node; + + switch (expr->boolop) + { + /*-------------------- + * 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 each subclause. + * + * If the input is already AND/OR flat and has no NOT + * directly above AND or OR, this transformation preserves + * those properties. For example, if no direct child of + * the given AND clause is an AND or a NOT-above-OR, then + * the recursive calls of negate_clause() can't return any + * OR clauses. So we needn't call pull_ors() before + * building a new OR clause. Similarly for the OR case. + *-------------------- + */ + case AND_EXPR: + { + List *nargs = NIL; + ListCell *lc; + + foreach(lc, expr->args) + { + nargs = lappend(nargs, + negate_clause(lfirst(lc))); + } + return (Node *) make_orclause(nargs); + } + break; + case OR_EXPR: + { + List *nargs = NIL; + ListCell *lc; + + foreach(lc, expr->args) + { + nargs = lappend(nargs, + negate_clause(lfirst(lc))); + } + return (Node *) make_andclause(nargs); + } + break; + case NOT_EXPR: + /* + * NOT underneath NOT: they cancel. We assume the + * input is already simplified, so no need to recurse. + */ + return (Node *) linitial(expr->args); + default: + elog(ERROR, "unrecognized boolop: %d", + (int) expr->boolop); + break; + } + } + break; + case T_NullTest: + { + NullTest *expr = (NullTest *) node; + + /* + * In the rowtype case, the two flavors of NullTest are *not* + * logical inverses, so we can't simplify. But it does work + * for scalar datatypes. + */ + if (!expr->argisrow) + { + NullTest *newexpr = makeNode(NullTest); + + newexpr->arg = expr->arg; + newexpr->nulltesttype = (expr->nulltesttype == IS_NULL ? + IS_NOT_NULL : IS_NULL); + newexpr->argisrow = expr->argisrow; + return (Node *) newexpr; + } + } + break; + case T_BooleanTest: + { + BooleanTest *expr = (BooleanTest *) node; + BooleanTest *newexpr = makeNode(BooleanTest); + + newexpr->arg = expr->arg; + switch (expr->booltesttype) + { + case IS_TRUE: + newexpr->booltesttype = IS_NOT_TRUE; + break; + case IS_NOT_TRUE: + newexpr->booltesttype = IS_TRUE; + break; + case IS_FALSE: + newexpr->booltesttype = IS_NOT_FALSE; + break; + case IS_NOT_FALSE: + newexpr->booltesttype = IS_FALSE; + break; + case IS_UNKNOWN: + newexpr->booltesttype = IS_NOT_UNKNOWN; + break; + case IS_NOT_UNKNOWN: + newexpr->booltesttype = IS_UNKNOWN; + break; + default: + elog(ERROR, "unrecognized booltesttype: %d", + (int) expr->booltesttype); + break; + } + return (Node *) newexpr; + } + break; + default: + /* else fall through */ + break; + } + + /* + * Otherwise we don't know how to simplify this, so just tack on an + * explicit NOT node. + */ + return (Node *) make_notclause((Expr *) node); +} + + /* * canonicalize_qual * Convert a qualification expression to the most useful form. @@ -72,18 +287,11 @@ canonicalize_qual(Expr *qual) return NULL; /* - * Push down NOTs. We do this only in the top-level boolean expression, - * without examining arguments of operators/functions. The main reason for - * doing this is to expose as much top-level AND/OR structure as we can, - * so there's no point in descending further. + * Pull up redundant subclauses in OR-of-AND trees. We do this only + * within the top-level AND/OR structure; there's no point in looking + * deeper. */ - newqual = find_nots(qual); - - /* - * Pull up redundant subclauses in OR-of-AND trees. Again, we do this - * only within the top-level AND/OR structure. - */ - newqual = find_duplicate_ors(newqual); + newqual = find_duplicate_ors(qual); return newqual; } @@ -154,147 +362,6 @@ pull_ors(List *orlist) } -/* - * find_nots - * Traverse the qualification, looking for NOTs to take care of. - * For NOT clauses, apply push_nots() to try to push down the NOT. - * For AND and OR clause types, simply recurse. Otherwise stop - * recursing (we do not worry about structure below the top AND/OR tree). - * - * Returns the modified qualification. AND/OR flatness is preserved. - */ -static Expr * -find_nots(Expr *qual) -{ - if (and_clause((Node *) qual)) - { - List *t_list = NIL; - ListCell *temp; - - foreach(temp, ((BoolExpr *) qual)->args) - t_list = lappend(t_list, find_nots(lfirst(temp))); - return make_andclause(pull_ands(t_list)); - } - else if (or_clause((Node *) qual)) - { - List *t_list = NIL; - ListCell *temp; - - foreach(temp, ((BoolExpr *) qual)->args) - t_list = lappend(t_list, find_nots(lfirst(temp))); - return make_orclause(pull_ors(t_list)); - } - else if (not_clause((Node *) qual)) - return push_nots(get_notclausearg(qual)); - else - 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 (is_opclause(qual)) - { - /* - * 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). - */ - OpExpr *opexpr = (OpExpr *) qual; - Oid negator = get_negator(opexpr->opno); - - if (negator) - { - OpExpr *newopexpr = makeNode(OpExpr); - - newopexpr->opno = negator; - newopexpr->opfuncid = InvalidOid; - newopexpr->opresulttype = opexpr->opresulttype; - newopexpr->opretset = opexpr->opretset; - newopexpr->args = opexpr->args; - newopexpr->location = opexpr->location; - return (Expr *) newopexpr; - } - else - return make_notclause(qual); - } - else if (qual && IsA(qual, ScalarArrayOpExpr)) - { - /* - * Negate a ScalarArrayOpExpr if there is a negator for its operator; - * for example x = ANY (list) becomes x <> ALL (list). Otherwise, - * retain the clause as it is (the NOT can't be pushed down any - * farther). - */ - ScalarArrayOpExpr *saopexpr = (ScalarArrayOpExpr *) qual; - Oid negator = get_negator(saopexpr->opno); - - if (negator) - { - ScalarArrayOpExpr *newopexpr = makeNode(ScalarArrayOpExpr); - - newopexpr->opno = negator; - newopexpr->opfuncid = InvalidOid; - newopexpr->useOr = !saopexpr->useOr; - newopexpr->args = saopexpr->args; - newopexpr->location = saopexpr->location; - return (Expr *) newopexpr; - } - 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., swap AND for OR and negate all the subclauses. - *-------------------- - */ - List *t_list = NIL; - ListCell *temp; - - foreach(temp, ((BoolExpr *) qual)->args) - t_list = lappend(t_list, push_nots(lfirst(temp))); - return make_orclause(pull_ors(t_list)); - } - else if (or_clause((Node *) qual)) - { - List *t_list = NIL; - ListCell *temp; - - foreach(temp, ((BoolExpr *) qual)->args) - t_list = lappend(t_list, push_nots(lfirst(temp))); - return make_andclause(pull_ands(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 NOTs - * to simplify. - */ - return find_nots(get_notclausearg(qual)); - } - else - { - /* - * We don't know how to negate anything else, place a NOT at this - * level. No point in recursing deeper, either. - */ - return make_notclause(qual); - } -} - - /*-------------------- * The following code attempts to apply the inverse OR distributive law: * ((A AND B) OR (A AND C)) => (A AND (B OR C)) diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 16aaf876504..13e89ec6678 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -98,7 +98,7 @@ static List *simplify_or_arguments(List *args, static List *simplify_and_arguments(List *args, eval_const_expressions_context *context, bool *haveNull, bool *forceFalse); -static Expr *simplify_boolean_equality(Oid opno, List *args); +static Node *simplify_boolean_equality(Oid opno, List *args); static Expr *simplify_function(Oid funcid, Oid result_type, int32 result_typmod, List **args, bool has_named_args, @@ -2229,7 +2229,7 @@ eval_const_expressions_mutator(Node *node, if (expr->opno == BooleanEqualOperator || expr->opno == BooleanNotEqualOperator) { - simple = simplify_boolean_equality(expr->opno, args); + simple = (Expr *) simplify_boolean_equality(expr->opno, args); if (simple) /* successfully simplified it */ return (Node *) simple; } @@ -2395,24 +2395,12 @@ eval_const_expressions_mutator(Node *node, Assert(list_length(expr->args) == 1); arg = eval_const_expressions_mutator(linitial(expr->args), context); - if (IsA(arg, Const)) - { - Const *const_input = (Const *) arg; - /* NOT NULL => NULL */ - if (const_input->constisnull) - return makeBoolConst(false, true); - /* otherwise pretty easy */ - return makeBoolConst(!DatumGetBool(const_input->constvalue), - false); - } - else if (not_clause(arg)) - { - /* Cancel NOT/NOT */ - return (Node *) get_notclausearg((Expr *) arg); - } - /* Else we still need a NOT node */ - return (Node *) make_notclause((Expr *) arg); + /* + * Use negate_clause() to see if we can simplify away + * the NOT. + */ + return negate_clause(arg); } default: elog(ERROR, "unrecognized boolop: %d", @@ -3222,11 +3210,11 @@ simplify_and_arguments(List *args, * We come here only if simplify_function has failed; therefore we cannot * see two constant inputs, nor a constant-NULL input. */ -static Expr * +static Node * simplify_boolean_equality(Oid opno, List *args) { - Expr *leftop; - Expr *rightop; + Node *leftop; + Node *rightop; Assert(list_length(args) == 2); leftop = linitial(args); @@ -3239,12 +3227,12 @@ simplify_boolean_equality(Oid opno, List *args) if (DatumGetBool(((Const *) leftop)->constvalue)) return rightop; /* true = foo */ else - return make_notclause(rightop); /* false = foo */ + return negate_clause(rightop); /* false = foo */ } else { if (DatumGetBool(((Const *) leftop)->constvalue)) - return make_notclause(rightop); /* true <> foo */ + return negate_clause(rightop); /* true <> foo */ else return rightop; /* false <> foo */ } @@ -3257,12 +3245,12 @@ simplify_boolean_equality(Oid opno, List *args) if (DatumGetBool(((Const *) rightop)->constvalue)) return leftop; /* foo = true */ else - return make_notclause(leftop); /* foo = false */ + return negate_clause(leftop); /* foo = false */ } else { if (DatumGetBool(((Const *) rightop)->constvalue)) - return make_notclause(leftop); /* foo <> true */ + return negate_clause(leftop); /* foo <> true */ else return leftop; /* foo <> false */ } diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 372b79688f7..f8dd5428ee4 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -33,6 +33,7 @@ extern Relids get_relids_for_join(PlannerInfo *root, int joinrelid); /* * prototypes for prepqual.c */ +extern Node *negate_clause(Node *node); extern Expr *canonicalize_qual(Expr *qual); /*