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

Lines changed: 82 additions & 40 deletions
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

Lines changed: 17 additions & 0 deletions
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

Lines changed: 8 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy