Commit fffb5819 authored by Tom Lane's avatar Tom Lane

Adjust constant-folding of CASE expressions so that the simple comparison

form of CASE (eg, CASE 0 WHEN 1 THEN ...) can be constant-folded as it
was in 7.4.  Also, avoid constant-folding result expressions that are
certainly unreachable --- the former coding was a bit cavalier about this
and could generate unexpected results for all-constant CASE expressions.
Add regression test cases.  Per report from Vlad Marchenko.
parent b3a7e987
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 * HISTORY
* AUTHOR DATE MAJOR EVENT * AUTHOR DATE MAJOR EVENT
...@@ -49,6 +49,7 @@ ...@@ -49,6 +49,7 @@
typedef struct typedef struct
{ {
List *active_fns; List *active_fns;
Node *case_val;
bool estimate; bool estimate;
} eval_const_expressions_context; } eval_const_expressions_context;
...@@ -1195,6 +1196,7 @@ eval_const_expressions(Node *node) ...@@ -1195,6 +1196,7 @@ eval_const_expressions(Node *node)
eval_const_expressions_context context; eval_const_expressions_context context;
context.active_fns = NIL; /* nothing being recursively simplified */ context.active_fns = NIL; /* nothing being recursively simplified */
context.case_val = NULL; /* no CASE being examined */
context.estimate = false; /* safe transformations only */ context.estimate = false; /* safe transformations only */
return eval_const_expressions_mutator(node, &context); return eval_const_expressions_mutator(node, &context);
} }
...@@ -1219,6 +1221,7 @@ estimate_expression_value(Node *node) ...@@ -1219,6 +1221,7 @@ estimate_expression_value(Node *node)
eval_const_expressions_context context; eval_const_expressions_context context;
context.active_fns = NIL; /* nothing being recursively simplified */ context.active_fns = NIL; /* nothing being recursively simplified */
context.case_val = NULL; /* no CASE being examined */
context.estimate = true; /* unsafe transformations OK */ context.estimate = true; /* unsafe transformations OK */
return eval_const_expressions_mutator(node, &context); return eval_const_expressions_mutator(node, &context);
} }
...@@ -1592,71 +1595,98 @@ eval_const_expressions_mutator(Node *node, ...@@ -1592,71 +1595,98 @@ eval_const_expressions_mutator(Node *node,
* If there are no non-FALSE alternatives, we simplify the entire * If there are no non-FALSE alternatives, we simplify the entire
* CASE to the default result (ELSE result). * CASE to the default result (ELSE result).
* *
* If we have a simple-form CASE with constant test expression and * If we have a simple-form CASE with constant test expression,
* one or more constant comparison expressions, we could run the * we substitute the constant value for contained CaseTestExpr
* implied comparisons and potentially reduce those arms to constants. * placeholder nodes, so that we have the opportunity to reduce
* This is not yet implemented, however. At present, the * constant test conditions. For example this allows
* CaseTestExpr placeholder will always act as a non-constant node * CASE 0 WHEN 0 THEN 1 ELSE 1/0 END
* and prevent the comparison boolean expressions from being reduced * to reduce to 1 rather than drawing a divide-by-0 error.
* to Const nodes.
*---------- *----------
*/ */
CaseExpr *caseexpr = (CaseExpr *) node; CaseExpr *caseexpr = (CaseExpr *) node;
CaseExpr *newcase; CaseExpr *newcase;
Node *save_case_val;
Node *newarg; Node *newarg;
List *newargs; List *newargs;
Node *defresult; bool const_true_cond;
Const *const_input; Node *defresult = NULL;
ListCell *arg; ListCell *arg;
/* Simplify the test expression, if any */ /* Simplify the test expression, if any */
newarg = eval_const_expressions_mutator((Node *) caseexpr->arg, newarg = eval_const_expressions_mutator((Node *) caseexpr->arg,
context); 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 */ /* Simplify the WHEN clauses */
newargs = NIL; newargs = NIL;
const_true_cond = false;
foreach(arg, caseexpr->args) foreach(arg, caseexpr->args)
{ {
/* Simplify this alternative's condition and result */ CaseWhen *oldcasewhen = (CaseWhen *) lfirst(arg);
CaseWhen *casewhen = (CaseWhen *) Node *casecond;
expression_tree_mutator((Node *) lfirst(arg), Node *caseresult;
eval_const_expressions_mutator,
(void *) context); Assert(IsA(oldcasewhen, CaseWhen));
Assert(IsA(casewhen, CaseWhen)); /* Simplify this alternative's test condition */
if (casewhen->expr == NULL || casecond =
!IsA(casewhen->expr, Const)) eval_const_expressions_mutator((Node *) oldcasewhen->expr,
{ context);
newargs = lappend(newargs, casewhen);
continue;
}
const_input = (Const *) casewhen->expr;
if (const_input->constisnull ||
!DatumGetBool(const_input->constvalue))
continue; /* drop alternative with FALSE condition */
/* /*
* Found a TRUE condition. If it's the first (un-dropped) * If the test condition is constant FALSE (or NULL), then drop
* alternative, the CASE reduces to just this alternative. * this WHEN clause completely, without processing the result.
*/ */
if (newargs == NIL) if (casecond && IsA(casecond, Const))
return (Node *) casewhen->result; {
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; break;
} }
/* Simplify the default result */ /* Simplify the default result, unless we replaced it above */
defresult = eval_const_expressions_mutator((Node *) caseexpr->defresult, if (!const_true_cond)
context); defresult =
eval_const_expressions_mutator((Node *) caseexpr->defresult,
context);
/* context->case_val = save_case_val;
* If no non-FALSE alternatives, CASE reduces to the default
* result /* If no non-FALSE alternatives, CASE reduces to the default result */
*/
if (newargs == NIL) if (newargs == NIL)
return defresult; return defresult;
/* Otherwise we need a new CASE node */ /* Otherwise we need a new CASE node */
...@@ -1667,6 +1697,18 @@ eval_const_expressions_mutator(Node *node, ...@@ -1667,6 +1697,18 @@ eval_const_expressions_mutator(Node *node,
newcase->defresult = (Expr *) defresult; newcase->defresult = (Expr *) defresult;
return (Node *) newcase; 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)) if (IsA(node, ArrayExpr))
{ {
ArrayExpr *arrayexpr = (ArrayExpr *) node; ArrayExpr *arrayexpr = (ArrayExpr *) node;
......
...@@ -72,6 +72,23 @@ SELECT '6' AS "One", ...@@ -72,6 +72,23 @@ SELECT '6' AS "One",
6 | 6 6 | 6
(1 row) (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 -- Test for cases involving untyped literals in test expression
SELECT CASE 'a' WHEN 'a' THEN 1 ELSE 2 END; SELECT CASE 'a' WHEN 'a' THEN 1 ELSE 2 END;
case case
......
...@@ -58,6 +58,14 @@ SELECT '6' AS "One", ...@@ -58,6 +58,14 @@ SELECT '6' AS "One",
ELSE 7 ELSE 7
END AS "Two WHEN with default"; 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 -- Test for cases involving untyped literals in test expression
SELECT CASE 'a' WHEN 'a' THEN 1 ELSE 2 END; SELECT CASE 'a' WHEN 'a' THEN 1 ELSE 2 END;
......
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