Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit fffb581

Browse files
committed
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.
1 parent b3a7e98 commit fffb581

File tree

3 files changed

+107
-40
lines changed

3 files changed

+107
-40
lines changed

src/backend/optimizer/util/clauses.c

+82-40
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.187 2005/01/28 19:34:07 tgl Exp $
11+
* $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.188 2005/02/02 21:49:07 tgl Exp $
1212
*
1313
* HISTORY
1414
* AUTHOR DATE MAJOR EVENT
@@ -49,6 +49,7 @@
4949
typedef struct
5050
{
5151
List *active_fns;
52+
Node *case_val;
5253
bool estimate;
5354
} eval_const_expressions_context;
5455

@@ -1195,6 +1196,7 @@ eval_const_expressions(Node *node)
11951196
eval_const_expressions_context context;
11961197

11971198
context.active_fns = NIL; /* nothing being recursively simplified */
1199+
context.case_val = NULL; /* no CASE being examined */
11981200
context.estimate = false; /* safe transformations only */
11991201
return eval_const_expressions_mutator(node, &context);
12001202
}
@@ -1219,6 +1221,7 @@ estimate_expression_value(Node *node)
12191221
eval_const_expressions_context context;
12201222

12211223
context.active_fns = NIL; /* nothing being recursively simplified */
1224+
context.case_val = NULL; /* no CASE being examined */
12221225
context.estimate = true; /* unsafe transformations OK */
12231226
return eval_const_expressions_mutator(node, &context);
12241227
}
@@ -1592,71 +1595,98 @@ eval_const_expressions_mutator(Node *node,
15921595
* If there are no non-FALSE alternatives, we simplify the entire
15931596
* CASE to the default result (ELSE result).
15941597
*
1595-
* If we have a simple-form CASE with constant test expression and
1596-
* one or more constant comparison expressions, we could run the
1597-
* implied comparisons and potentially reduce those arms to constants.
1598-
* This is not yet implemented, however. At present, the
1599-
* CaseTestExpr placeholder will always act as a non-constant node
1600-
* and prevent the comparison boolean expressions from being reduced
1601-
* to Const nodes.
1598+
* If we have a simple-form CASE with constant test expression,
1599+
* we substitute the constant value for contained CaseTestExpr
1600+
* placeholder nodes, so that we have the opportunity to reduce
1601+
* constant test conditions. For example this allows
1602+
* CASE 0 WHEN 0 THEN 1 ELSE 1/0 END
1603+
* to reduce to 1 rather than drawing a divide-by-0 error.
16021604
*----------
16031605
*/
16041606
CaseExpr *caseexpr = (CaseExpr *) node;
16051607
CaseExpr *newcase;
1608+
Node *save_case_val;
16061609
Node *newarg;
16071610
List *newargs;
1608-
Node *defresult;
1609-
Const *const_input;
1611+
bool const_true_cond;
1612+
Node *defresult = NULL;
16101613
ListCell *arg;
16111614

16121615
/* Simplify the test expression, if any */
16131616
newarg = eval_const_expressions_mutator((Node *) caseexpr->arg,
16141617
context);
16151618

1619+
/* Set up for contained CaseTestExpr nodes */
1620+
save_case_val = context->case_val;
1621+
if (newarg && IsA(newarg, Const))
1622+
context->case_val = newarg;
1623+
else
1624+
context->case_val = NULL;
1625+
16161626
/* Simplify the WHEN clauses */
16171627
newargs = NIL;
1628+
const_true_cond = false;
16181629
foreach(arg, caseexpr->args)
16191630
{
1620-
/* Simplify this alternative's condition and result */
1621-
CaseWhen *casewhen = (CaseWhen *)
1622-
expression_tree_mutator((Node *) lfirst(arg),
1623-
eval_const_expressions_mutator,
1624-
(void *) context);
1625-
1626-
Assert(IsA(casewhen, CaseWhen));
1627-
if (casewhen->expr == NULL ||
1628-
!IsA(casewhen->expr, Const))
1629-
{
1630-
newargs = lappend(newargs, casewhen);
1631-
continue;
1632-
}
1633-
const_input = (Const *) casewhen->expr;
1634-
if (const_input->constisnull ||
1635-
!DatumGetBool(const_input->constvalue))
1636-
continue; /* drop alternative with FALSE condition */
1631+
CaseWhen *oldcasewhen = (CaseWhen *) lfirst(arg);
1632+
Node *casecond;
1633+
Node *caseresult;
1634+
1635+
Assert(IsA(oldcasewhen, CaseWhen));
1636+
1637+
/* Simplify this alternative's test condition */
1638+
casecond =
1639+
eval_const_expressions_mutator((Node *) oldcasewhen->expr,
1640+
context);
16371641

16381642
/*
1639-
* Found a TRUE condition. If it's the first (un-dropped)
1640-
* alternative, the CASE reduces to just this alternative.
1643+
* If the test condition is constant FALSE (or NULL), then drop
1644+
* this WHEN clause completely, without processing the result.
16411645
*/
1642-
if (newargs == NIL)
1643-
return (Node *) casewhen->result;
1646+
if (casecond && IsA(casecond, Const))
1647+
{
1648+
Const *const_input = (Const *) casecond;
1649+
1650+
if (const_input->constisnull ||
1651+
!DatumGetBool(const_input->constvalue))
1652+
continue; /* drop alternative with FALSE condition */
1653+
/* Else it's constant TRUE */
1654+
const_true_cond = true;
1655+
}
1656+
1657+
/* Simplify this alternative's result value */
1658+
caseresult =
1659+
eval_const_expressions_mutator((Node *) oldcasewhen->result,
1660+
context);
16441661

1662+
/* If non-constant test condition, emit a new WHEN node */
1663+
if (!const_true_cond)
1664+
{
1665+
CaseWhen *newcasewhen = makeNode(CaseWhen);
1666+
1667+
newcasewhen->expr = (Expr *) casecond;
1668+
newcasewhen->result = (Expr *) caseresult;
1669+
newargs = lappend(newargs, newcasewhen);
1670+
continue;
1671+
}
1672+
16451673
/*
1646-
* Otherwise, add it to the list, and drop all the rest.
1674+
* Found a TRUE condition, so none of the remaining alternatives
1675+
* can be reached. We treat the result as the default result.
16471676
*/
1648-
newargs = lappend(newargs, casewhen);
1677+
defresult = caseresult;
16491678
break;
16501679
}
16511680

1652-
/* Simplify the default result */
1653-
defresult = eval_const_expressions_mutator((Node *) caseexpr->defresult,
1654-
context);
1681+
/* Simplify the default result, unless we replaced it above */
1682+
if (!const_true_cond)
1683+
defresult =
1684+
eval_const_expressions_mutator((Node *) caseexpr->defresult,
1685+
context);
16551686

1656-
/*
1657-
* If no non-FALSE alternatives, CASE reduces to the default
1658-
* result
1659-
*/
1687+
context->case_val = save_case_val;
1688+
1689+
/* If no non-FALSE alternatives, CASE reduces to the default result */
16601690
if (newargs == NIL)
16611691
return defresult;
16621692
/* Otherwise we need a new CASE node */
@@ -1667,6 +1697,18 @@ eval_const_expressions_mutator(Node *node,
16671697
newcase->defresult = (Expr *) defresult;
16681698
return (Node *) newcase;
16691699
}
1700+
if (IsA(node, CaseTestExpr))
1701+
{
1702+
/*
1703+
* If we know a constant test value for the current CASE
1704+
* construct, substitute it for the placeholder. Else just
1705+
* return the placeholder as-is.
1706+
*/
1707+
if (context->case_val)
1708+
return copyObject(context->case_val);
1709+
else
1710+
return copyObject(node);
1711+
}
16701712
if (IsA(node, ArrayExpr))
16711713
{
16721714
ArrayExpr *arrayexpr = (ArrayExpr *) node;

src/test/regress/expected/case.out

+17
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,23 @@ SELECT '6' AS "One",
7272
6 | 6
7373
(1 row)
7474

75+
-- Constant-expression folding shouldn't evaluate unreachable subexpressions
76+
SELECT CASE WHEN 1=0 THEN 1/0 WHEN 1=1 THEN 1 ELSE 2/0 END;
77+
case
78+
------
79+
1
80+
(1 row)
81+
82+
SELECT CASE 1 WHEN 0 THEN 1/0 WHEN 1 THEN 1 ELSE 2/0 END;
83+
case
84+
------
85+
1
86+
(1 row)
87+
88+
-- However we do not currently suppress folding of potentially
89+
-- reachable subexpressions
90+
SELECT CASE WHEN i > 100 THEN 1/0 ELSE 0 END FROM case_tbl;
91+
ERROR: division by zero
7592
-- Test for cases involving untyped literals in test expression
7693
SELECT CASE 'a' WHEN 'a' THEN 1 ELSE 2 END;
7794
case

src/test/regress/sql/case.sql

+8
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ SELECT '6' AS "One",
5858
ELSE 7
5959
END AS "Two WHEN with default";
6060

61+
-- Constant-expression folding shouldn't evaluate unreachable subexpressions
62+
SELECT CASE WHEN 1=0 THEN 1/0 WHEN 1=1 THEN 1 ELSE 2/0 END;
63+
SELECT CASE 1 WHEN 0 THEN 1/0 WHEN 1 THEN 1 ELSE 2/0 END;
64+
65+
-- However we do not currently suppress folding of potentially
66+
-- reachable subexpressions
67+
SELECT CASE WHEN i > 100 THEN 1/0 ELSE 0 END FROM case_tbl;
68+
6169
-- Test for cases involving untyped literals in test expression
6270
SELECT CASE 'a' WHEN 'a' THEN 1 ELSE 2 END;
6371

0 commit comments

Comments
 (0)