diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index a7ea96ec72d252dc73c897d5a7c6d0603dc8c22b..acf948816d071c28bc502734d24fb9b33bb4c705 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.187 2005/01/28 19:34:07 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.188 2005/02/02 21:49:07 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -49,6 +49,7 @@ typedef struct { List *active_fns; + Node *case_val; bool estimate; } eval_const_expressions_context; @@ -1195,6 +1196,7 @@ eval_const_expressions(Node *node) eval_const_expressions_context context; context.active_fns = NIL; /* nothing being recursively simplified */ + context.case_val = NULL; /* no CASE being examined */ context.estimate = false; /* safe transformations only */ return eval_const_expressions_mutator(node, &context); } @@ -1219,6 +1221,7 @@ estimate_expression_value(Node *node) eval_const_expressions_context context; context.active_fns = NIL; /* nothing being recursively simplified */ + context.case_val = NULL; /* no CASE being examined */ context.estimate = true; /* unsafe transformations OK */ return eval_const_expressions_mutator(node, &context); } @@ -1592,71 +1595,98 @@ eval_const_expressions_mutator(Node *node, * If there are no non-FALSE alternatives, we simplify the entire * CASE to the default result (ELSE result). * - * If we have a simple-form CASE with constant test expression and - * one or more constant comparison expressions, we could run the - * implied comparisons and potentially reduce those arms to constants. - * This is not yet implemented, however. At present, the - * CaseTestExpr placeholder will always act as a non-constant node - * and prevent the comparison boolean expressions from being reduced - * to Const nodes. + * If we have a simple-form CASE with constant test expression, + * we substitute the constant value for contained CaseTestExpr + * placeholder nodes, so that we have the opportunity to reduce + * constant test conditions. For example this allows + * CASE 0 WHEN 0 THEN 1 ELSE 1/0 END + * to reduce to 1 rather than drawing a divide-by-0 error. *---------- */ CaseExpr *caseexpr = (CaseExpr *) node; CaseExpr *newcase; + Node *save_case_val; Node *newarg; List *newargs; - Node *defresult; - Const *const_input; + bool const_true_cond; + Node *defresult = NULL; ListCell *arg; /* Simplify the test expression, if any */ newarg = eval_const_expressions_mutator((Node *) caseexpr->arg, context); + /* Set up for contained CaseTestExpr nodes */ + save_case_val = context->case_val; + if (newarg && IsA(newarg, Const)) + context->case_val = newarg; + else + context->case_val = NULL; + /* Simplify the WHEN clauses */ newargs = NIL; + const_true_cond = false; foreach(arg, caseexpr->args) { - /* Simplify this alternative's condition and result */ - CaseWhen *casewhen = (CaseWhen *) - expression_tree_mutator((Node *) lfirst(arg), - eval_const_expressions_mutator, - (void *) context); - - Assert(IsA(casewhen, CaseWhen)); - if (casewhen->expr == NULL || - !IsA(casewhen->expr, Const)) - { - newargs = lappend(newargs, casewhen); - continue; - } - const_input = (Const *) casewhen->expr; - if (const_input->constisnull || - !DatumGetBool(const_input->constvalue)) - continue; /* drop alternative with FALSE condition */ + CaseWhen *oldcasewhen = (CaseWhen *) lfirst(arg); + Node *casecond; + Node *caseresult; + + Assert(IsA(oldcasewhen, CaseWhen)); + + /* Simplify this alternative's test condition */ + casecond = + eval_const_expressions_mutator((Node *) oldcasewhen->expr, + context); /* - * Found a TRUE condition. If it's the first (un-dropped) - * alternative, the CASE reduces to just this alternative. + * If the test condition is constant FALSE (or NULL), then drop + * this WHEN clause completely, without processing the result. */ - if (newargs == NIL) - return (Node *) casewhen->result; + if (casecond && IsA(casecond, Const)) + { + Const *const_input = (Const *) casecond; + + if (const_input->constisnull || + !DatumGetBool(const_input->constvalue)) + continue; /* drop alternative with FALSE condition */ + /* Else it's constant TRUE */ + const_true_cond = true; + } + + /* Simplify this alternative's result value */ + caseresult = + eval_const_expressions_mutator((Node *) oldcasewhen->result, + context); + /* If non-constant test condition, emit a new WHEN node */ + if (!const_true_cond) + { + CaseWhen *newcasewhen = makeNode(CaseWhen); + + newcasewhen->expr = (Expr *) casecond; + newcasewhen->result = (Expr *) caseresult; + newargs = lappend(newargs, newcasewhen); + continue; + } + /* - * Otherwise, add it to the list, and drop all the rest. + * Found a TRUE condition, so none of the remaining alternatives + * can be reached. We treat the result as the default result. */ - newargs = lappend(newargs, casewhen); + defresult = caseresult; break; } - /* Simplify the default result */ - defresult = eval_const_expressions_mutator((Node *) caseexpr->defresult, - context); + /* Simplify the default result, unless we replaced it above */ + if (!const_true_cond) + defresult = + eval_const_expressions_mutator((Node *) caseexpr->defresult, + context); - /* - * If no non-FALSE alternatives, CASE reduces to the default - * result - */ + context->case_val = save_case_val; + + /* If no non-FALSE alternatives, CASE reduces to the default result */ if (newargs == NIL) return defresult; /* Otherwise we need a new CASE node */ @@ -1667,6 +1697,18 @@ eval_const_expressions_mutator(Node *node, newcase->defresult = (Expr *) defresult; return (Node *) newcase; } + if (IsA(node, CaseTestExpr)) + { + /* + * If we know a constant test value for the current CASE + * construct, substitute it for the placeholder. Else just + * return the placeholder as-is. + */ + if (context->case_val) + return copyObject(context->case_val); + else + return copyObject(node); + } if (IsA(node, ArrayExpr)) { ArrayExpr *arrayexpr = (ArrayExpr *) node; diff --git a/src/test/regress/expected/case.out b/src/test/regress/expected/case.out index df3fb094b3ec470186a642d6ca0f1452282051eb..9ec32b8bd26a2f9749756c4b5d56851a36790072 100644 --- a/src/test/regress/expected/case.out +++ b/src/test/regress/expected/case.out @@ -72,6 +72,23 @@ SELECT '6' AS "One", 6 | 6 (1 row) +-- Constant-expression folding shouldn't evaluate unreachable subexpressions +SELECT CASE WHEN 1=0 THEN 1/0 WHEN 1=1 THEN 1 ELSE 2/0 END; + case +------ + 1 +(1 row) + +SELECT CASE 1 WHEN 0 THEN 1/0 WHEN 1 THEN 1 ELSE 2/0 END; + case +------ + 1 +(1 row) + +-- However we do not currently suppress folding of potentially +-- reachable subexpressions +SELECT CASE WHEN i > 100 THEN 1/0 ELSE 0 END FROM case_tbl; +ERROR: division by zero -- Test for cases involving untyped literals in test expression SELECT CASE 'a' WHEN 'a' THEN 1 ELSE 2 END; case diff --git a/src/test/regress/sql/case.sql b/src/test/regress/sql/case.sql index 85e17e0807f496d5da079409ccc3dda9155fef5e..2ab22fb1c6d812fb544241169a0bf73966cb3a1c 100644 --- a/src/test/regress/sql/case.sql +++ b/src/test/regress/sql/case.sql @@ -58,6 +58,14 @@ SELECT '6' AS "One", ELSE 7 END AS "Two WHEN with default"; +-- Constant-expression folding shouldn't evaluate unreachable subexpressions +SELECT CASE WHEN 1=0 THEN 1/0 WHEN 1=1 THEN 1 ELSE 2/0 END; +SELECT CASE 1 WHEN 0 THEN 1/0 WHEN 1 THEN 1 ELSE 2/0 END; + +-- However we do not currently suppress folding of potentially +-- reachable subexpressions +SELECT CASE WHEN i > 100 THEN 1/0 ELSE 0 END FROM case_tbl; + -- Test for cases involving untyped literals in test expression SELECT CASE 'a' WHEN 'a' THEN 1 ELSE 2 END;