Adjust constant-folding of CASE expressions so that the simple comparison
authorTom Lane
Wed, 2 Feb 2005 21:49:09 +0000 (21:49 +0000)
committerTom Lane
Wed, 2 Feb 2005 21:49:09 +0000 (21:49 +0000)
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.

src/backend/optimizer/util/clauses.c
src/test/regress/expected/case.out
src/test/regress/sql/case.sql

index a7ea96ec72d252dc73c897d5a7c6d0603dc8c22b..acf948816d071c28bc502734d24fb9b33bb4c705 100644 (file)
@@ -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;
index df3fb094b3ec470186a642d6ca0f1452282051eb..9ec32b8bd26a2f9749756c4b5d56851a36790072 100644 (file)
@@ -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 
index 85e17e0807f496d5da079409ccc3dda9155fef5e..2ab22fb1c6d812fb544241169a0bf73966cb3a1c 100644 (file)
@@ -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;