Turns out that my recent elimination of the 'redundant' flatten_andors()
code in prepqual.c had a small drawback: the flatten_andors code was able to cope with deeply nested AND/OR structures (like 10000 ORs in a row), whereas eval_const_expressions tends to recurse until it overruns the stack. Revise eval_const_expressions so that it doesn't choke on deeply nested ANDs or ORs.
This commit is contained in:
parent
25434e32cb
commit
56c8877291
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.195 2005/04/14 21:44:09 tgl Exp $
|
* $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.196 2005/04/23 04:42:53 tgl Exp $
|
||||||
*
|
*
|
||||||
* HISTORY
|
* HISTORY
|
||||||
* AUTHOR DATE MAJOR EVENT
|
* AUTHOR DATE MAJOR EVENT
|
||||||
@ -73,8 +73,10 @@ static bool set_coercionform_dontcare_walker(Node *node, void *context);
|
|||||||
static Node *eval_const_expressions_mutator(Node *node,
|
static Node *eval_const_expressions_mutator(Node *node,
|
||||||
eval_const_expressions_context *context);
|
eval_const_expressions_context *context);
|
||||||
static List *simplify_or_arguments(List *args,
|
static List *simplify_or_arguments(List *args,
|
||||||
|
eval_const_expressions_context *context,
|
||||||
bool *haveNull, bool *forceTrue);
|
bool *haveNull, bool *forceTrue);
|
||||||
static List *simplify_and_arguments(List *args,
|
static List *simplify_and_arguments(List *args,
|
||||||
|
eval_const_expressions_context *context,
|
||||||
bool *haveNull, bool *forceFalse);
|
bool *haveNull, bool *forceFalse);
|
||||||
static Expr *simplify_boolean_equality(List *args);
|
static Expr *simplify_boolean_equality(List *args);
|
||||||
static Expr *simplify_function(Oid funcid, Oid result_type, List *args,
|
static Expr *simplify_function(Oid funcid, Oid result_type, List *args,
|
||||||
@ -1466,16 +1468,6 @@ eval_const_expressions_mutator(Node *node,
|
|||||||
if (IsA(node, BoolExpr))
|
if (IsA(node, BoolExpr))
|
||||||
{
|
{
|
||||||
BoolExpr *expr = (BoolExpr *) node;
|
BoolExpr *expr = (BoolExpr *) node;
|
||||||
List *args;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Reduce constants in the BoolExpr's arguments. We know args is
|
|
||||||
* either NIL or a List node, so we can call
|
|
||||||
* expression_tree_mutator directly rather than recursing to self.
|
|
||||||
*/
|
|
||||||
args = (List *) expression_tree_mutator((Node *) expr->args,
|
|
||||||
eval_const_expressions_mutator,
|
|
||||||
(void *) context);
|
|
||||||
|
|
||||||
switch (expr->boolop)
|
switch (expr->boolop)
|
||||||
{
|
{
|
||||||
@ -1485,7 +1477,7 @@ eval_const_expressions_mutator(Node *node,
|
|||||||
bool haveNull = false;
|
bool haveNull = false;
|
||||||
bool forceTrue = false;
|
bool forceTrue = false;
|
||||||
|
|
||||||
newargs = simplify_or_arguments(args,
|
newargs = simplify_or_arguments(expr->args, context,
|
||||||
&haveNull, &forceTrue);
|
&haveNull, &forceTrue);
|
||||||
if (forceTrue)
|
if (forceTrue)
|
||||||
return makeBoolConst(true, false);
|
return makeBoolConst(true, false);
|
||||||
@ -1506,7 +1498,7 @@ eval_const_expressions_mutator(Node *node,
|
|||||||
bool haveNull = false;
|
bool haveNull = false;
|
||||||
bool forceFalse = false;
|
bool forceFalse = false;
|
||||||
|
|
||||||
newargs = simplify_and_arguments(args,
|
newargs = simplify_and_arguments(expr->args, context,
|
||||||
&haveNull, &forceFalse);
|
&haveNull, &forceFalse);
|
||||||
if (forceFalse)
|
if (forceFalse)
|
||||||
return makeBoolConst(false, false);
|
return makeBoolConst(false, false);
|
||||||
@ -1522,10 +1514,15 @@ eval_const_expressions_mutator(Node *node,
|
|||||||
return (Node *) make_andclause(newargs);
|
return (Node *) make_andclause(newargs);
|
||||||
}
|
}
|
||||||
case NOT_EXPR:
|
case NOT_EXPR:
|
||||||
Assert(list_length(args) == 1);
|
|
||||||
if (IsA(linitial(args), Const))
|
|
||||||
{
|
{
|
||||||
Const *const_input = (Const *) linitial(args);
|
Node *arg;
|
||||||
|
|
||||||
|
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 */
|
/* NOT NULL => NULL */
|
||||||
if (const_input->constisnull)
|
if (const_input->constisnull)
|
||||||
@ -1534,13 +1531,14 @@ eval_const_expressions_mutator(Node *node,
|
|||||||
return makeBoolConst(!DatumGetBool(const_input->constvalue),
|
return makeBoolConst(!DatumGetBool(const_input->constvalue),
|
||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
else if (not_clause((Node *) linitial(args)))
|
else if (not_clause(arg))
|
||||||
{
|
{
|
||||||
/* Cancel NOT/NOT */
|
/* Cancel NOT/NOT */
|
||||||
return (Node *) get_notclausearg((Expr *) linitial(args));
|
return (Node *) get_notclausearg((Expr *) arg);
|
||||||
}
|
}
|
||||||
/* Else we still need a NOT node */
|
/* Else we still need a NOT node */
|
||||||
return (Node *) make_notclause((Expr *) linitial(args));
|
return (Node *) make_notclause((Expr *) arg);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
elog(ERROR, "unrecognized boolop: %d",
|
elog(ERROR, "unrecognized boolop: %d",
|
||||||
(int) expr->boolop);
|
(int) expr->boolop);
|
||||||
@ -1869,33 +1867,87 @@ eval_const_expressions_mutator(Node *node,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Subroutine for eval_const_expressions: scan the arguments of an OR clause
|
* Subroutine for eval_const_expressions: process arguments of an OR clause
|
||||||
*
|
*
|
||||||
* OR arguments are handled as follows:
|
* This includes flattening of nested ORs as well as recursion to
|
||||||
|
* eval_const_expressions to simplify the OR arguments.
|
||||||
|
*
|
||||||
|
* After simplification, OR arguments are handled as follows:
|
||||||
* non constant: keep
|
* non constant: keep
|
||||||
* FALSE: drop (does not affect result)
|
* FALSE: drop (does not affect result)
|
||||||
* TRUE: force result to TRUE
|
* TRUE: force result to TRUE
|
||||||
* NULL: keep only one
|
* NULL: keep only one
|
||||||
* We must keep one NULL input because ExecEvalOr returns NULL when no input
|
* We must keep one NULL input because ExecEvalOr returns NULL when no input
|
||||||
* is TRUE and at least one is NULL.
|
* is TRUE and at least one is NULL. We don't actually include the NULL
|
||||||
*
|
* here, that's supposed to be done by the caller.
|
||||||
* This is split out as a subroutine so that we can recurse to fold sub-ORs
|
|
||||||
* into the upper OR clause, thereby ensuring that nested ORs are flattened.
|
|
||||||
*
|
*
|
||||||
* The output arguments *haveNull and *forceTrue must be initialized FALSE
|
* The output arguments *haveNull and *forceTrue must be initialized FALSE
|
||||||
* by the caller. They will be set TRUE if a null constant or true constant,
|
* by the caller. They will be set TRUE if a null constant or true constant,
|
||||||
* respectively, is detected anywhere in the argument list.
|
* respectively, is detected anywhere in the argument list.
|
||||||
*/
|
*/
|
||||||
static List *
|
static List *
|
||||||
simplify_or_arguments(List *args, bool *haveNull, bool *forceTrue)
|
simplify_or_arguments(List *args,
|
||||||
|
eval_const_expressions_context *context,
|
||||||
|
bool *haveNull, bool *forceTrue)
|
||||||
{
|
{
|
||||||
List *newargs = NIL;
|
List *newargs = NIL;
|
||||||
ListCell *larg;
|
List *unprocessed_args;
|
||||||
|
|
||||||
foreach(larg, args)
|
/*
|
||||||
|
* Since the parser considers OR to be a binary operator, long OR lists
|
||||||
|
* become deeply nested expressions. We must flatten these into long
|
||||||
|
* argument lists of a single OR operator. To avoid blowing out the stack
|
||||||
|
* with recursion of eval_const_expressions, we resort to some tenseness
|
||||||
|
* here: we keep a list of not-yet-processed inputs, and handle flattening
|
||||||
|
* of nested ORs by prepending to the to-do list instead of recursing.
|
||||||
|
*/
|
||||||
|
unprocessed_args = list_copy(args);
|
||||||
|
while (unprocessed_args)
|
||||||
{
|
{
|
||||||
Node *arg = (Node *) lfirst(larg);
|
Node *arg = (Node *) linitial(unprocessed_args);
|
||||||
|
|
||||||
|
unprocessed_args = list_delete_first(unprocessed_args);
|
||||||
|
|
||||||
|
/* flatten nested ORs as per above comment */
|
||||||
|
if (or_clause(arg))
|
||||||
|
{
|
||||||
|
List *subargs = list_copy(((BoolExpr *) arg)->args);
|
||||||
|
|
||||||
|
/* overly tense code to avoid leaking unused list header */
|
||||||
|
if (!unprocessed_args)
|
||||||
|
unprocessed_args = subargs;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
List *oldhdr = unprocessed_args;
|
||||||
|
|
||||||
|
unprocessed_args = list_concat(subargs, unprocessed_args);
|
||||||
|
pfree(oldhdr);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If it's not an OR, simplify it */
|
||||||
|
arg = eval_const_expressions_mutator(arg, context);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* It is unlikely but not impossible for simplification of a
|
||||||
|
* non-OR clause to produce an OR. Recheck, but don't be
|
||||||
|
* too tense about it since it's not a mainstream case.
|
||||||
|
* In particular we don't worry about const-simplifying
|
||||||
|
* the input twice.
|
||||||
|
*/
|
||||||
|
if (or_clause(arg))
|
||||||
|
{
|
||||||
|
List *subargs = list_copy(((BoolExpr *) arg)->args);
|
||||||
|
|
||||||
|
unprocessed_args = list_concat(subargs, unprocessed_args);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* OK, we have a const-simplified non-OR argument. Process it
|
||||||
|
* per comments above.
|
||||||
|
*/
|
||||||
if (IsA(arg, Const))
|
if (IsA(arg, Const))
|
||||||
{
|
{
|
||||||
Const *const_input = (Const *) arg;
|
Const *const_input = (Const *) arg;
|
||||||
@ -1914,14 +1966,10 @@ simplify_or_arguments(List *args, bool *haveNull, bool *forceTrue)
|
|||||||
return NIL;
|
return NIL;
|
||||||
}
|
}
|
||||||
/* otherwise, we can drop the constant-false input */
|
/* otherwise, we can drop the constant-false input */
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
else if (or_clause(arg))
|
|
||||||
{
|
/* else emit the simplified arg into the result list */
|
||||||
newargs = list_concat(newargs,
|
|
||||||
simplify_or_arguments(((BoolExpr *) arg)->args,
|
|
||||||
haveNull, forceTrue));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
newargs = lappend(newargs, arg);
|
newargs = lappend(newargs, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1929,33 +1977,80 @@ simplify_or_arguments(List *args, bool *haveNull, bool *forceTrue)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Subroutine for eval_const_expressions: scan the arguments of an AND clause
|
* Subroutine for eval_const_expressions: process arguments of an AND clause
|
||||||
*
|
*
|
||||||
* AND arguments are handled as follows:
|
* This includes flattening of nested ANDs as well as recursion to
|
||||||
|
* eval_const_expressions to simplify the AND arguments.
|
||||||
|
*
|
||||||
|
* After simplification, AND arguments are handled as follows:
|
||||||
* non constant: keep
|
* non constant: keep
|
||||||
* TRUE: drop (does not affect result)
|
* TRUE: drop (does not affect result)
|
||||||
* FALSE: force result to FALSE
|
* FALSE: force result to FALSE
|
||||||
* NULL: keep only one
|
* NULL: keep only one
|
||||||
* We must keep one NULL input because ExecEvalAnd returns NULL when no input
|
* We must keep one NULL input because ExecEvalAnd returns NULL when no input
|
||||||
* is FALSE and at least one is NULL.
|
* is FALSE and at least one is NULL. We don't actually include the NULL
|
||||||
*
|
* here, that's supposed to be done by the caller.
|
||||||
* This is split out as a subroutine so that we can recurse to fold sub-ANDs
|
|
||||||
* into the upper AND clause, thereby ensuring that nested ANDs are flattened.
|
|
||||||
*
|
*
|
||||||
* The output arguments *haveNull and *forceFalse must be initialized FALSE
|
* The output arguments *haveNull and *forceFalse must be initialized FALSE
|
||||||
* by the caller. They will be set TRUE if a null constant or false constant,
|
* by the caller. They will be set TRUE if a null constant or false constant,
|
||||||
* respectively, is detected anywhere in the argument list.
|
* respectively, is detected anywhere in the argument list.
|
||||||
*/
|
*/
|
||||||
static List *
|
static List *
|
||||||
simplify_and_arguments(List *args, bool *haveNull, bool *forceFalse)
|
simplify_and_arguments(List *args,
|
||||||
|
eval_const_expressions_context *context,
|
||||||
|
bool *haveNull, bool *forceFalse)
|
||||||
{
|
{
|
||||||
List *newargs = NIL;
|
List *newargs = NIL;
|
||||||
ListCell *larg;
|
List *unprocessed_args;
|
||||||
|
|
||||||
foreach(larg, args)
|
/* See comments in simplify_or_arguments */
|
||||||
|
unprocessed_args = list_copy(args);
|
||||||
|
while (unprocessed_args)
|
||||||
{
|
{
|
||||||
Node *arg = (Node *) lfirst(larg);
|
Node *arg = (Node *) linitial(unprocessed_args);
|
||||||
|
|
||||||
|
unprocessed_args = list_delete_first(unprocessed_args);
|
||||||
|
|
||||||
|
/* flatten nested ANDs as per above comment */
|
||||||
|
if (and_clause(arg))
|
||||||
|
{
|
||||||
|
List *subargs = list_copy(((BoolExpr *) arg)->args);
|
||||||
|
|
||||||
|
/* overly tense code to avoid leaking unused list header */
|
||||||
|
if (!unprocessed_args)
|
||||||
|
unprocessed_args = subargs;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
List *oldhdr = unprocessed_args;
|
||||||
|
|
||||||
|
unprocessed_args = list_concat(subargs, unprocessed_args);
|
||||||
|
pfree(oldhdr);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If it's not an AND, simplify it */
|
||||||
|
arg = eval_const_expressions_mutator(arg, context);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* It is unlikely but not impossible for simplification of a
|
||||||
|
* non-AND clause to produce an AND. Recheck, but don't be
|
||||||
|
* too tense about it since it's not a mainstream case.
|
||||||
|
* In particular we don't worry about const-simplifying
|
||||||
|
* the input twice.
|
||||||
|
*/
|
||||||
|
if (and_clause(arg))
|
||||||
|
{
|
||||||
|
List *subargs = list_copy(((BoolExpr *) arg)->args);
|
||||||
|
|
||||||
|
unprocessed_args = list_concat(subargs, unprocessed_args);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* OK, we have a const-simplified non-AND argument. Process it
|
||||||
|
* per comments above.
|
||||||
|
*/
|
||||||
if (IsA(arg, Const))
|
if (IsA(arg, Const))
|
||||||
{
|
{
|
||||||
Const *const_input = (Const *) arg;
|
Const *const_input = (Const *) arg;
|
||||||
@ -1974,14 +2069,10 @@ simplify_and_arguments(List *args, bool *haveNull, bool *forceFalse)
|
|||||||
return NIL;
|
return NIL;
|
||||||
}
|
}
|
||||||
/* otherwise, we can drop the constant-true input */
|
/* otherwise, we can drop the constant-true input */
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
else if (and_clause(arg))
|
|
||||||
{
|
/* else emit the simplified arg into the result list */
|
||||||
newargs = list_concat(newargs,
|
|
||||||
simplify_and_arguments(((BoolExpr *) arg)->args,
|
|
||||||
haveNull, forceFalse));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
newargs = lappend(newargs, arg);
|
newargs = lappend(newargs, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user