]> granicus.if.org Git - postgresql/commitdiff
Support SQL/PSM-compatible CASE statement in plpgsql.
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 15 May 2008 22:39:49 +0000 (22:39 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 15 May 2008 22:39:49 +0000 (22:39 +0000)
Pavel Stehule

doc/src/sgml/errcodes.sgml
doc/src/sgml/plpgsql.sgml
src/include/utils/errcodes.h
src/pl/plpgsql/src/gram.y
src/pl/plpgsql/src/pl_exec.c
src/pl/plpgsql/src/pl_funcs.c
src/pl/plpgsql/src/plerrcodes.h
src/pl/plpgsql/src/plpgsql.h
src/pl/plpgsql/src/scan.l
src/test/regress/expected/plpgsql.out
src/test/regress/sql/plpgsql.sql

index a3f56a86b14bd26463f8995c25d8f0f008ae3e42..474c0ca8da711eb96f446898b1f41dcdb11c5f7b 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/errcodes.sgml,v 1.23 2008/01/15 01:36:53 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/errcodes.sgml,v 1.24 2008/05/15 22:39:48 tgl Exp $ -->
 
 <appendix id="errcodes-appendix">
  <title><productname>PostgreSQL</productname> Error Codes</title>
 
  <tgroup cols="3">
   <colspec colnum="1" colname="errorcode">
-  <colspec colnum="3" colname="constant">
-  <spanspec namest="errorcode" nameend="constant" spanname="span13">
+  <colspec colnum="3" colname="condname">
+  <spanspec namest="errorcode" nameend="condname" spanname="span13">
 
   <thead>
    <row>
     <entry>Error Code</entry>
     <entry>Meaning</entry>
-    <entry>Constant</entry>
+    <entry>Condition Name</entry>
    </row>
   </thead>
 
 </row>
 
 
+<row>
+<entry spanname="span13"><emphasis role="bold">Class 20 &mdash; Case Not Found</></entry>
+</row>
+
+<row>
+<entry><literal>20000</literal></entry>
+<entry>CASE NOT FOUND</entry>
+<entry>case_not_found</entry>
+</row>
+
+
 <row>
 <entry spanname="span13"><emphasis role="bold">Class 21 &mdash; Cardinality Violation</></entry>
 </row>
index 09ad6944dba5a065745f46346149e3084a4e6121..d2853d2d901b2644b40963c22eb34dd6d2ce7d12 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.129 2008/05/13 22:10:29 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.130 2008/05/15 22:39:49 tgl Exp $ -->
 
 <chapter id="plpgsql">
   <title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title>
@@ -1581,9 +1581,9 @@ SELECT * FROM getallfoo();
     <title>Conditionals</title>
 
     <para>
-     <literal>IF</> statements let you execute commands based on
-     certain conditions.  <application>PL/pgSQL</> has five forms of
-     <literal>IF</>:
+     <command>IF</> and <command>CASE</> statements let you execute
+     alternative commands based on certain conditions.
+     <application>PL/pgSQL</> has five forms of <command>IF</>:
     <itemizedlist>
      <listitem>
       <para><literal>IF ... THEN</></>
@@ -1601,6 +1601,22 @@ SELECT * FROM getallfoo();
       <para><literal>IF ... THEN ... ELSEIF ... THEN ... ELSE</></>
      </listitem>
     </itemizedlist>
+
+    and four forms of <command>CASE</>:
+    <itemizedlist>
+     <listitem>
+      <para><literal>CASE ... WHEN ... THEN ... END CASE</></>
+     </listitem>
+     <listitem>
+      <para><literal>CASE ... WHEN ... THEN ... ELSE ... END CASE</></>
+     </listitem>
+     <listitem>
+      <para><literal>CASE WHEN ... THEN ... END CASE</></>
+     </listitem>
+     <listitem>
+      <para><literal>CASE WHEN ... THEN ... ELSE ... END CASE</></>
+     </listitem>
+    </itemizedlist>
     </para>
 
     <sect3>
@@ -1751,6 +1767,93 @@ END IF;
        <literal>ELSEIF</> is an alias for <literal>ELSIF</>.
       </para>
      </sect3>
+
+     <sect3>
+      <title>Simple <literal>CASE</></title>
+
+<synopsis>
+CASE <replaceable>search-expression</replaceable>
+    WHEN <replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> <optional> ... </optional></optional> THEN
+      <replaceable>statements</replaceable>
+  <optional> WHEN <replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> <optional> ... </optional></optional> THEN
+      <replaceable>statements</replaceable>
+    ... </optional>
+  <optional> ELSE
+      <replaceable>statements</replaceable> </optional>
+END CASE;
+</synopsis>
+
+      <para>
+       The simple form of <command>CASE</> provides conditional execution
+       based on equality of operands.  The <replaceable>search-expression</>
+       is evaluated (once) and successively compared to each
+       <replaceable>expression</> in the <literal>WHEN</> clauses.
+       If a match is found, then the corresponding
+       <replaceable>statements</replaceable> are executed, and then control
+       passes to the next statement after <literal>END CASE</>.  (Subsequent
+       <literal>WHEN</> expressions are not evaluated.)  If no match is
+       found, the <literal>ELSE</> <replaceable>statements</replaceable> are
+       executed; but if <literal>ELSE</> is not present, then a
+       <literal>CASE_NOT_FOUND</literal> exception is raised.
+      </para>
+
+      <para>
+       Here is a simple example:
+
+<programlisting>
+CASE x
+    WHEN 1, 2 THEN
+        msg := 'one or two';
+    ELSE
+        msg := 'other value than one or two';
+END CASE;
+</programlisting>
+      </para>
+     </sect3>
+
+     <sect3>
+      <title>Searched <literal>CASE</></title>
+
+<synopsis>
+CASE
+    WHEN <replaceable>boolean-expression</replaceable> THEN
+      <replaceable>statements</replaceable>
+  <optional> WHEN <replaceable>boolean-expression</replaceable> THEN
+      <replaceable>statements</replaceable>
+    ... </optional>
+  <optional> ELSE
+      <replaceable>statements</replaceable> </optional>
+END CASE;
+</synopsis>
+
+      <para>
+       The searched form of <command>CASE</> provides conditional execution
+       based on truth of boolean expressions.  Each <literal>WHEN</> clause's
+       <replaceable>boolean-expression</replaceable> is evaluated in turn,
+       until one is found that yields <literal>true</>.  Then the
+       corresponding <replaceable>statements</replaceable> are executed, and
+       then control passes to the next statement after <literal>END CASE</>.
+       (Subsequent <literal>WHEN</> expressions are not evaluated.)
+       If no true result is found, the <literal>ELSE</>
+       <replaceable>statements</replaceable> are executed;
+       but if <literal>ELSE</> is not present, then a
+       <literal>CASE_NOT_FOUND</literal> exception is raised.
+      </para>
+
+      <para>
+       Here is an example:
+
+<programlisting>
+CASE
+    WHEN x BETWEEN 0 AND 10 THEN
+        msg := 'value is between zero and ten';
+    WHEN x BETWEEN 11 AND 20 THEN
+        msg := 'value is between eleven and twenty';
+END CASE;
+</programlisting>
+      </para>
+
+     </sect3>
    </sect2>
 
    <sect2 id="plpgsql-control-structures-loops">
index 1caaf69f2d76c30e7b89d090c4127dcdbb1bb93c..9cfdd16bde2454747eafb828e9a4fa071b154446 100644 (file)
@@ -11,7 +11,7 @@
  *
  * Copyright (c) 2003-2008, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.24 2008/01/01 19:45:59 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.25 2008/05/15 22:39:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 /* Class 0P - Invalid Role Specification */
 #define ERRCODE_INVALID_ROLE_SPECIFICATION     MAKE_SQLSTATE('0','P', '0','0','0')
 
+/* Class 20 - Case Not Found */
+#define ERRCODE_CASE_NOT_FOUND                         MAKE_SQLSTATE('2','0', '0','0','0')
+
 /* Class 21 - Cardinality Violation */
 /* (this means something returned the wrong number of rows) */
 #define ERRCODE_CARDINALITY_VIOLATION          MAKE_SQLSTATE('2','1', '0','0','0')
index b67a8bba776a28a493401ce233ee038e70678a4c..dfd37f67717a426d8374a32ce25a246e18b6e003 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.112 2008/05/13 22:10:29 tgl Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.113 2008/05/15 22:39:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -37,6 +37,8 @@ static        PLpgSQL_stmt_fetch *read_fetch_direction(void);
 static PLpgSQL_stmt    *make_return_stmt(int lineno);
 static PLpgSQL_stmt    *make_return_next_stmt(int lineno);
 static PLpgSQL_stmt    *make_return_query_stmt(int lineno);
+static  PLpgSQL_stmt   *make_case(int lineno, PLpgSQL_expr *t_expr,
+                                                                  List *case_when_list, List *else_stmts);
 static void                     check_assignable(PLpgSQL_datum *datum);
 static void                     read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row,
                                                                                  bool *strict);
@@ -102,6 +104,7 @@ static List                         *read_raise_options(void);
                PLpgSQL_nsitem                  *nsitem;
                PLpgSQL_diag_item               *diagitem;
                PLpgSQL_stmt_fetch              *fetch;
+               PLpgSQL_case_when               *casewhen;
 }
 
 %type <declhdr> decl_sect
@@ -116,7 +119,7 @@ static List                         *read_raise_options(void);
 %type <str>            decl_stmts decl_stmt
 
 %type <expr>   expr_until_semi expr_until_rightbracket
-%type <expr>   expr_until_then expr_until_loop
+%type <expr>   expr_until_then expr_until_loop opt_expr_until_when
 %type <expr>   opt_exitcond
 
 %type <ival>   assign_var
@@ -135,12 +138,16 @@ static List                               *read_raise_options(void);
 %type <stmt>   stmt_return stmt_raise stmt_execsql stmt_execsql_insert
 %type <stmt>   stmt_dynexecute stmt_for stmt_perform stmt_getdiag
 %type <stmt>   stmt_open stmt_fetch stmt_move stmt_close stmt_null
+%type <stmt>   stmt_case
 
 %type <list>   proc_exceptions
 %type <exception_block> exception_sect
 %type <exception>      proc_exception
 %type <condition>      proc_conditions proc_condition
 
+%type <casewhen>       case_when
+%type <list>   case_when_list opt_case_else
+
 %type <list>   getdiag_list
 %type <diagitem> getdiag_list_item
 %type <ival>   getdiag_kind getdiag_target
@@ -157,6 +164,7 @@ static List                         *read_raise_options(void);
 %token K_ASSIGN
 %token K_BEGIN
 %token K_BY
+%token K_CASE
 %token K_CLOSE
 %token K_CONSTANT
 %token K_CONTINUE
@@ -581,9 +589,7 @@ decl_defkey         : K_ASSIGN
                                ;
 
 proc_sect              :
-                                       {
-                                               $$ = NIL;
-                                       }
+                                       { $$ = NIL; }
                                | proc_stmts
                                        { $$ = $1; }
                                ;
@@ -598,7 +604,7 @@ proc_stmts          : proc_stmts proc_stmt
                                | proc_stmt
                                                {
                                                        if ($1 == NULL)
-                                                               $$ = NULL;
+                                                               $$ = NIL;
                                                        else
                                                                $$ = list_make1($1);
                                                }
@@ -610,6 +616,8 @@ proc_stmt           : pl_block ';'
                                                { $$ = $1; }
                                | stmt_if
                                                { $$ = $1; }
+                               | stmt_case
+                                               { $$ = $1; }
                                | stmt_loop
                                                { $$ = $1; }
                                | stmt_while
@@ -808,6 +816,67 @@ stmt_else          :
                                        }
                                ;
 
+stmt_case              : K_CASE lno opt_expr_until_when case_when_list opt_case_else K_END K_CASE ';'
+                                       {
+                                               $$ = make_case($2, $3, $4, $5);
+                                       }
+                               ;
+
+opt_expr_until_when    :
+                                       {
+                                               PLpgSQL_expr *expr = NULL;
+                                               int     tok = yylex();
+
+                                               if (tok != K_WHEN)
+                                               {
+                                                       plpgsql_push_back_token(tok);
+                                                       expr = plpgsql_read_expression(K_WHEN, "WHEN");
+                                               }
+                                               plpgsql_push_back_token(K_WHEN);
+                                               $$ = expr;
+                                       }
+                           ;
+
+case_when_list : case_when_list case_when
+                                       {
+                                               $$ = lappend($1, $2);
+                                       }
+                               | case_when
+                                       {
+                                               $$ = list_make1($1);
+                                       }
+                               ;
+
+case_when              : K_WHEN lno expr_until_then proc_sect
+                                       {
+                                               PLpgSQL_case_when *new = palloc(sizeof(PLpgSQL_case_when));
+
+                                               new->lineno     = $2;
+                                               new->expr       = $3;
+                                               new->stmts      = $4;
+                                               $$ = new;
+                                       }
+                               ;
+
+opt_case_else  :
+                                       {
+                                               $$ = NIL;
+                                       }
+                               | K_ELSE proc_sect
+                                       {
+                                               /*
+                                                * proc_sect could return an empty list, but we
+                                                * must distinguish that from not having ELSE at all.
+                                                * Simplest fix is to return a list with one NULL
+                                                * pointer, which make_case() must take care of.
+                                                */
+                                               if ($2 != NIL)
+                                                       $$ = $2;
+                                               else
+                                                       $$ = list_make1(NULL);
+                                       }
+                               ;
+
 stmt_loop              : opt_block_label K_LOOP lno loop_body
                                        {
                                                PLpgSQL_stmt_loop *new;
@@ -2804,6 +2873,103 @@ read_raise_options(void)
        return result;
 }
 
+/*
+ * Fix up CASE statement
+ */
+static PLpgSQL_stmt *
+make_case(int lineno, PLpgSQL_expr *t_expr,
+                 List *case_when_list, List *else_stmts)
+{
+       PLpgSQL_stmt_case       *new;
+
+       new = palloc(sizeof(PLpgSQL_stmt_case));
+       new->cmd_type = PLPGSQL_STMT_CASE;
+       new->lineno = lineno;
+       new->t_expr = t_expr;
+       new->t_varno = 0;
+       new->case_when_list = case_when_list;
+       new->have_else = (else_stmts != NIL);
+       /* Get rid of list-with-NULL hack */
+       if (list_length(else_stmts) == 1 && linitial(else_stmts) == NULL)
+               new->else_stmts = NIL;
+       else
+               new->else_stmts = else_stmts;
+
+       /*
+        * When test expression is present, we create a var for it and then
+        * convert all the WHEN expressions to "VAR IN (original_expression)".
+        * This is a bit klugy, but okay since we haven't yet done more than
+        * read the expressions as text.  (Note that previous parsing won't
+        * have complained if the WHEN ... THEN expression contained multiple
+        * comma-separated values.)
+        */
+       if (t_expr)
+       {
+               ListCell *l;
+               PLpgSQL_var *t_var;
+               int             t_varno;
+
+               /*
+                * We don't yet know the result datatype of t_expr.  Build the
+                * variable as if it were INT4; we'll fix this at runtime if needed.
+                */
+               t_var = (PLpgSQL_var *)
+                       plpgsql_build_variable("*case*", lineno,
+                                                                  plpgsql_build_datatype(INT4OID, -1),
+                                                                  false);
+               t_varno = t_var->varno;
+               new->t_varno = t_varno;
+
+               foreach(l, case_when_list)
+               {
+                       PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
+                       PLpgSQL_expr *expr = cwt->expr;
+                       int             nparams = expr->nparams;
+                       PLpgSQL_expr *new_expr;
+                       PLpgSQL_dstring ds;
+                       char    buff[32];
+
+                       /* Must add the CASE variable as an extra param to expression */
+                       if (nparams >= MAX_EXPR_PARAMS)
+                       {
+                               plpgsql_error_lineno = cwt->lineno;
+                               ereport(ERROR,
+                                           (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+                                            errmsg("too many variables specified in SQL statement")));
+                       }
+
+                       new_expr = palloc(sizeof(PLpgSQL_expr) + sizeof(int) * (nparams + 1) - sizeof(int));
+                       memcpy(new_expr, expr,
+                                  sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int));
+                       new_expr->nparams = nparams + 1;
+                       new_expr->params[nparams] = t_varno;
+
+                       /* And do the string hacking */
+                       plpgsql_dstring_init(&ds);
+
+                       plpgsql_dstring_append(&ds, "SELECT $");
+                       snprintf(buff, sizeof(buff), "%d", nparams + 1);
+                       plpgsql_dstring_append(&ds, buff);
+                       plpgsql_dstring_append(&ds, " IN (");
+
+                       /* copy expression query without SELECT keyword */
+                       Assert(strncmp(expr->query, "SELECT ", 7) == 0);
+                       plpgsql_dstring_append(&ds, expr->query + 7);
+                       plpgsql_dstring_append_char(&ds, ')');
+
+                       new_expr->query = pstrdup(plpgsql_dstring_get(&ds));
+
+                       plpgsql_dstring_free(&ds);
+                       pfree(expr->query);
+                       pfree(expr);
+
+                       cwt->expr = new_expr;
+               }
+       }
+
+       return (PLpgSQL_stmt *) new;
+}
+
 
 /* Needed to avoid conflict between different prefix settings: */
 #undef yylex
index 2ba45befb71e899473ec68a39918e8a119fcec90..aeb5d365b495a3eb976bae9439ccd078c203d078 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.214 2008/05/13 22:10:30 tgl Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.215 2008/05/15 22:39:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -94,6 +94,8 @@ static int exec_stmt_getdiag(PLpgSQL_execstate *estate,
                                  PLpgSQL_stmt_getdiag *stmt);
 static int exec_stmt_if(PLpgSQL_execstate *estate,
                         PLpgSQL_stmt_if *stmt);
+static int exec_stmt_case(PLpgSQL_execstate *estate,
+                                                 PLpgSQL_stmt_case *stmt);
 static int exec_stmt_loop(PLpgSQL_execstate *estate,
                           PLpgSQL_stmt_loop *stmt);
 static int exec_stmt_while(PLpgSQL_execstate *estate,
@@ -1229,7 +1231,7 @@ exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
 
        CHECK_FOR_INTERRUPTS();
 
-       switch (stmt->cmd_type)
+       switch ((enum PLpgSQL_stmt_types) stmt->cmd_type)
        {
                case PLPGSQL_STMT_BLOCK:
                        rc = exec_stmt_block(estate, (PLpgSQL_stmt_block *) stmt);
@@ -1251,6 +1253,10 @@ exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
                        rc = exec_stmt_if(estate, (PLpgSQL_stmt_if *) stmt);
                        break;
 
+               case PLPGSQL_STMT_CASE:
+                       rc = exec_stmt_case(estate, (PLpgSQL_stmt_case *) stmt);
+                       break;
+
                case PLPGSQL_STMT_LOOP:
                        rc = exec_stmt_loop(estate, (PLpgSQL_stmt_loop *) stmt);
                        break;
@@ -1442,6 +1448,91 @@ exec_stmt_if(PLpgSQL_execstate *estate, PLpgSQL_stmt_if *stmt)
 }
 
 
+/*-----------
+ * exec_stmt_case
+ *-----------
+ */
+static int
+exec_stmt_case(PLpgSQL_execstate *estate, PLpgSQL_stmt_case *stmt)
+{
+       PLpgSQL_var *t_var = NULL;
+       bool            isnull;
+       ListCell   *l;
+
+       if (stmt->t_expr != NULL)
+       {
+               /* simple case */
+               Datum   t_val;
+               Oid             t_oid;
+
+               t_val = exec_eval_expr(estate, stmt->t_expr, &isnull, &t_oid);
+
+               t_var = (PLpgSQL_var *) estate->datums[stmt->t_varno];
+
+               /*
+                * When expected datatype is different from real, change it.
+                * Note that what we're modifying here is an execution copy
+                * of the datum, so this doesn't affect the originally stored
+                * function parse tree.
+                */
+               if (t_var->datatype->typoid != t_oid)
+                       t_var->datatype = plpgsql_build_datatype(t_oid, -1);
+
+               /* now we can assign to the variable */
+               exec_assign_value(estate,
+                                                 (PLpgSQL_datum *) t_var,
+                                                 t_val,
+                                                 t_oid,
+                                                 &isnull);
+
+               exec_eval_cleanup(estate);
+       }
+
+       /* Now search for a successful WHEN clause */
+       foreach(l, stmt->case_when_list)
+       {
+               PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
+               bool    value;
+
+               value = exec_eval_boolean(estate, cwt->expr, &isnull);
+               exec_eval_cleanup(estate);
+               if (!isnull && value)
+               {
+                       /* Found it */
+
+                       /* We can now discard any value we had for the temp variable */
+                       if (t_var != NULL)
+                       {
+                               free_var(t_var);
+                               t_var->value = (Datum) 0;
+                               t_var->isnull = true;
+                       }
+
+                       /* Evaluate the statement(s), and we're done */
+                       return exec_stmts(estate, cwt->stmts);
+               }
+       }
+
+       /* We can now discard any value we had for the temp variable */
+       if (t_var != NULL)
+       {
+               free_var(t_var);
+               t_var->value = (Datum) 0;
+               t_var->isnull = true;
+       }
+
+       /* SQL2003 mandates this error if there was no ELSE clause */
+       if (!stmt->have_else)
+               ereport(ERROR,
+                               (errcode(ERRCODE_CASE_NOT_FOUND),
+                                errmsg("case not found"),
+                                errhint("CASE statement is missing ELSE part.")));
+
+       /* Evaluate the ELSE statements, and we're done */
+       return exec_stmts(estate, stmt->else_stmts);
+}
+
+
 /* ----------
  * exec_stmt_loop                      Loop over statements until
  *                                     an exit occurs.
index cb9e9c99ec3e33e1aea7f048997557fc80c7126c..aab349bdb2c0359b06cf9f4dd96c020045616e4f 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.71 2008/05/13 22:10:30 tgl Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.72 2008/05/15 22:39:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -466,7 +466,7 @@ plpgsql_convert_ident(const char *s, char **output, int numidents)
 const char *
 plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
 {
-       switch (stmt->cmd_type)
+       switch ((enum PLpgSQL_stmt_types) stmt->cmd_type)
        {
                case PLPGSQL_STMT_BLOCK:
                        return _("statement block");
@@ -474,6 +474,8 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
                        return _("assignment");
                case PLPGSQL_STMT_IF:
                        return "IF";
+               case PLPGSQL_STMT_CASE:
+                       return "CASE";
                case PLPGSQL_STMT_LOOP:
                        return "LOOP";
                case PLPGSQL_STMT_WHILE:
@@ -526,6 +528,7 @@ static void dump_stmt(PLpgSQL_stmt *stmt);
 static void dump_block(PLpgSQL_stmt_block *block);
 static void dump_assign(PLpgSQL_stmt_assign *stmt);
 static void dump_if(PLpgSQL_stmt_if *stmt);
+static void dump_case(PLpgSQL_stmt_case *stmt);
 static void dump_loop(PLpgSQL_stmt_loop *stmt);
 static void dump_while(PLpgSQL_stmt_while *stmt);
 static void dump_fori(PLpgSQL_stmt_fori *stmt);
@@ -561,7 +564,7 @@ static void
 dump_stmt(PLpgSQL_stmt *stmt)
 {
        printf("%3d:", stmt->lineno);
-       switch (stmt->cmd_type)
+       switch ((enum PLpgSQL_stmt_types) stmt->cmd_type)
        {
                case PLPGSQL_STMT_BLOCK:
                        dump_block((PLpgSQL_stmt_block *) stmt);
@@ -572,6 +575,9 @@ dump_stmt(PLpgSQL_stmt *stmt)
                case PLPGSQL_STMT_IF:
                        dump_if((PLpgSQL_stmt_if *) stmt);
                        break;
+               case PLPGSQL_STMT_CASE:
+                       dump_case((PLpgSQL_stmt_case *) stmt);
+                       break;
                case PLPGSQL_STMT_LOOP:
                        dump_loop((PLpgSQL_stmt_loop *) stmt);
                        break;
@@ -714,6 +720,44 @@ dump_if(PLpgSQL_stmt_if *stmt)
        printf("    ENDIF\n");
 }
 
+static void
+dump_case(PLpgSQL_stmt_case *stmt)
+{
+       ListCell        *l;
+
+       dump_ind();
+       printf("CASE %d ", stmt->t_varno);
+       if (stmt->t_expr)
+               dump_expr(stmt->t_expr);
+       printf("\n");
+       dump_indent += 6;
+       foreach(l, stmt->case_when_list)
+       {
+               PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
+
+               dump_ind();
+               printf("WHEN ");
+               dump_expr(cwt->expr);
+               printf("\n");
+               dump_ind();
+               printf("THEN\n");
+               dump_indent += 2;
+               dump_stmts(cwt->stmts);
+               dump_indent -= 2;
+       }
+       if (stmt->have_else)
+       {
+               dump_ind();
+               printf("ELSE\n");
+               dump_indent += 2;
+               dump_stmts(stmt->else_stmts);
+               dump_indent -= 2;
+       }
+       dump_indent -= 6;
+       dump_ind();
+       printf("    ENDCASE\n");
+}
+
 static void
 dump_loop(PLpgSQL_stmt_loop *stmt)
 {
@@ -1025,7 +1069,7 @@ dump_raise(PLpgSQL_stmt_raise *stmt)
                foreach(lc, stmt->options)
                {
                        PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(lc);
-               
+
                        dump_ind();
                        switch (opt->opt_type)
                        {
@@ -1034,7 +1078,7 @@ dump_raise(PLpgSQL_stmt_raise *stmt)
                                        break;
                                case PLPGSQL_RAISEOPTION_MESSAGE:
                                        printf("    MESSAGE = ");
-                                       break;  
+                                       break;
                                case PLPGSQL_RAISEOPTION_DETAIL:
                                        printf("    DETAIL = ");
                                        break;
@@ -1044,7 +1088,7 @@ dump_raise(PLpgSQL_stmt_raise *stmt)
                        }
                        dump_expr(opt->expr);
                        printf("\n");
-               }               
+               }
                dump_indent -= 2;
        }
        dump_indent -= 2;
index 5fbdf255d820756c8d664cb15026c48639bdba0f..e6ab24dfd0e9990898377084983e98cce0c04264 100644 (file)
@@ -9,7 +9,7 @@
  *
  * Copyright (c) 2003-2008, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/plerrcodes.h,v 1.13 2008/01/15 01:36:53 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/plerrcodes.h,v 1.14 2008/05/15 22:39:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
        "invalid_role_specification", ERRCODE_INVALID_ROLE_SPECIFICATION
 },
 
+{
+       "case_not_found", ERRCODE_CASE_NOT_FOUND
+},
+
 {
        "cardinality_violation", ERRCODE_CARDINALITY_VIOLATION
 },
index 53d691a596ab091226e9261f916bb721929f2582..735a6810053bdf3249a97442fb4ef90638712088 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.99 2008/05/13 22:10:30 tgl Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.100 2008/05/15 22:39:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -71,11 +71,12 @@ enum
  * Execution tree node types
  * ----------
  */
-enum
+enum PLpgSQL_stmt_types
 {
        PLPGSQL_STMT_BLOCK,
        PLPGSQL_STMT_ASSIGN,
        PLPGSQL_STMT_IF,
+       PLPGSQL_STMT_CASE,
        PLPGSQL_STMT_LOOP,
        PLPGSQL_STMT_WHILE,
        PLPGSQL_STMT_FORI,
@@ -390,6 +391,25 @@ typedef struct
 } PLpgSQL_stmt_if;
 
 
+typedef struct                                 /* CASE statement */
+{
+       int                     cmd_type;
+       int                     lineno;
+       PLpgSQL_expr *t_expr;           /* test expression, or NULL if none */
+       int                     t_varno;                /* var to store test expression value into */
+       List       *case_when_list;     /* List of PLpgSQL_case_when structs */
+       bool            have_else;              /* flag needed because list could be empty */
+       List       *else_stmts;         /* List of statements */
+} PLpgSQL_stmt_case;
+
+typedef struct                                 /* one arm of CASE statement */
+{
+       int                     lineno;
+       PLpgSQL_expr *expr;                     /* boolean expression for this case */
+       List       *stmts;                      /* List of statements */
+} PLpgSQL_case_when;
+
+
 typedef struct
 {                                                              /* Unconditional LOOP statement         */
        int                     cmd_type;
index 73258ec364802ac675f8fde39b708a17665d0bb3..77e9335a0529f6dc8095a45bb330a02302dcbc23 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.63 2008/05/13 22:10:30 tgl Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.64 2008/05/15 22:39:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -116,6 +116,7 @@ dolqinside          [^$]+
 alias                  { return K_ALIAS;                       }
 begin                  { return K_BEGIN;                       }
 by                             { return K_BY;                          }
+case                   { return K_CASE;                        }
 close                  { return K_CLOSE;                       }
 constant               { return K_CONSTANT;            }
 continue               { return K_CONTINUE;            }
index 9f18f45126acca0b96b8fec3a536873836969cba..582c06785a8aee50c04bcbd95756676da4ff9335 100644 (file)
@@ -2,24 +2,24 @@
 -- PLPGSQL
 --
 -- Scenario:
--- 
+--
 --     A building with a modern TP cable installation where any
 --     of the wall connectors can be used to plug in phones,
 --     ethernet interfaces or local office hubs. The backside
 --     of the wall connectors is wired to one of several patch-
 --     fields in the building.
--- 
+--
 --     In the patchfields, there are hubs and all the slots
 --     representing the wall connectors. In addition there are
 --     slots that can represent a phone line from the central
 --     phone system.
--- 
+--
 --     Triggers ensure consistency of the patching information.
--- 
+--
 --     Functions are used to build up powerful views that let
 --     you look behind the wall when looking at a patchfield
 --     or into a room.
--- 
+--
 create table Room (
     roomno     char(8),
     comment    text
@@ -84,10 +84,10 @@ create table PHone (
 );
 create unique index PHone_name on PHone using btree (slotname bpchar_ops);
 -- ************************************************************
--- * 
+-- *
 -- * Trigger procedures and functions for the patchfield
 -- * test of PL/pgSQL
--- * 
+-- *
 -- ************************************************************
 -- ************************************************************
 -- * AFTER UPDATE on Room
@@ -597,11 +597,11 @@ begin
     mytype := substr(myname, 1, 2);
     link := mytype || substr(blname, 1, 2);
     if link = ''PLPL'' then
-        raise exception 
+        raise exception
                ''backlink between two phone lines does not make sense'';
     end if;
     if link in (''PLWS'', ''WSPL'') then
-        raise exception 
+        raise exception
                ''direct link of phone line to wall slot not permitted'';
     end if;
     if mytype = ''PS'' then
@@ -745,19 +745,19 @@ begin
     mytype := substr(myname, 1, 2);
     link := mytype || substr(blname, 1, 2);
     if link = ''PHPH'' then
-        raise exception 
+        raise exception
                ''slotlink between two phones does not make sense'';
     end if;
     if link in (''PHHS'', ''HSPH'') then
-        raise exception 
+        raise exception
                ''link of phone to hub does not make sense'';
     end if;
     if link in (''PHIF'', ''IFPH'') then
-        raise exception 
+        raise exception
                ''link of phone to hub does not make sense'';
     end if;
     if link in (''PSWS'', ''WSPS'') then
-        raise exception 
+        raise exception
                ''slotlink from patchslot to wallslot not permitted'';
     end if;
     if mytype = ''PS'' then
@@ -2936,7 +2936,7 @@ CONTEXT:  PL/pgSQL function "footest" line 4 at EXECUTE statement
 drop function footest();
 -- test scrollable cursor support
 create function sc_test() returns setof integer as $$
-declare 
+declare
   c scroll cursor for select f1 from int4_tbl;
   x integer;
 begin
@@ -2960,7 +2960,7 @@ select * from sc_test();
 (5 rows)
 
 create or replace function sc_test() returns setof integer as $$
-declare 
+declare
   c no scroll cursor for select f1 from int4_tbl;
   x integer;
 begin
@@ -2978,7 +2978,7 @@ ERROR:  cursor can only scan forward
 HINT:  Declare it with SCROLL option to enable backward scan.
 CONTEXT:  PL/pgSQL function "sc_test" line 6 at FETCH
 create or replace function sc_test() returns setof integer as $$
-declare 
+declare
   c refcursor;
   x integer;
 begin
@@ -3002,7 +3002,7 @@ select * from sc_test();
 (5 rows)
 
 create or replace function sc_test() returns setof integer as $$
-declare 
+declare
   c refcursor;
   x integer;
 begin
@@ -3288,9 +3288,9 @@ drop function return_dquery();
 -- Tests for 8.4's new RAISE features
 create or replace function raise_test() returns void as $$
 begin
-  raise notice '% % %', 1, 2, 3 
+  raise notice '% % %', 1, 2, 3
      using errcode = '55001', detail = 'some detail info', hint = 'some hint';
-  raise '% % %', 1, 2, 3 
+  raise '% % %', 1, 2, 3
      using errcode = 'division_by_zero', detail = 'some detail info';
 end;
 $$ language plpgsql;
@@ -3414,3 +3414,133 @@ select raise_test();
 ERROR:  RAISE without parameters cannot be used outside an exception handler
 CONTEXT:  PL/pgSQL function "raise_test"
 drop function raise_test();
+-- test CASE statement
+create or replace function case_test(bigint) returns text as $$
+declare a int = 10;
+        b int = 1;
+begin
+  case $1
+    when 1 then
+      return 'one';
+    when 2 then
+      return 'two';
+    when 3,4,3+5 then
+      return 'three, four or eight';
+    when a then
+      return 'ten';
+    when a+b, a+b+1 then
+      return 'eleven, twelve';
+  end case;
+end;
+$$ language plpgsql immutable;
+select case_test(1);
+ case_test 
+-----------
+ one
+(1 row)
+
+select case_test(2);
+ case_test 
+-----------
+ two
+(1 row)
+
+select case_test(3);
+      case_test       
+----------------------
+ three, four or eight
+(1 row)
+
+select case_test(4);
+      case_test       
+----------------------
+ three, four or eight
+(1 row)
+
+select case_test(5); -- fails
+ERROR:  case not found
+HINT:  CASE statement is missing ELSE part.
+CONTEXT:  PL/pgSQL function "case_test" line 4 at CASE
+select case_test(8);
+      case_test       
+----------------------
+ three, four or eight
+(1 row)
+
+select case_test(10);
+ case_test 
+-----------
+ ten
+(1 row)
+
+select case_test(11);
+   case_test    
+----------------
+ eleven, twelve
+(1 row)
+
+select case_test(12);
+   case_test    
+----------------
+ eleven, twelve
+(1 row)
+
+select case_test(13); -- fails
+ERROR:  case not found
+HINT:  CASE statement is missing ELSE part.
+CONTEXT:  PL/pgSQL function "case_test" line 4 at CASE
+create or replace function catch() returns void as $$
+begin
+  raise notice '%', case_test(6);
+exception
+  when case_not_found then
+    raise notice 'caught case_not_found % %', SQLSTATE, SQLERRM;
+end
+$$ language plpgsql;
+select catch();
+NOTICE:  caught case_not_found 20000 case not found
+ catch 
+-------
+(1 row)
+
+-- test the searched variant too, as well as ELSE
+create or replace function case_test(bigint) returns text as $$
+declare a int = 10;
+begin
+  case
+    when $1 = 1 then
+      return 'one';
+    when $1 = a + 2 then
+      return 'twelve';
+    else
+      return 'other';
+  end case;
+end;
+$$ language plpgsql immutable;
+select case_test(1);
+ case_test 
+-----------
+ one
+(1 row)
+
+select case_test(2);
+ case_test 
+-----------
+ other
+(1 row)
+
+select case_test(12);
+ case_test 
+-----------
+ twelve
+(1 row)
+
+select case_test(13);
+ case_test 
+-----------
+ other
+(1 row)
+
+drop function catch();
+drop function case_test(bigint);
index f64bfabc1e5385d79f1d8c4af55e793151e06c59..0267dda30ca21662ee7dbe0b6cf659aab93c01d0 100644 (file)
@@ -2,24 +2,24 @@
 -- PLPGSQL
 --
 -- Scenario:
--- 
+--
 --     A building with a modern TP cable installation where any
 --     of the wall connectors can be used to plug in phones,
 --     ethernet interfaces or local office hubs. The backside
 --     of the wall connectors is wired to one of several patch-
 --     fields in the building.
--- 
+--
 --     In the patchfields, there are hubs and all the slots
 --     representing the wall connectors. In addition there are
 --     slots that can represent a phone line from the central
 --     phone system.
--- 
+--
 --     Triggers ensure consistency of the patching information.
--- 
+--
 --     Functions are used to build up powerful views that let
 --     you look behind the wall when looking at a patchfield
 --     or into a room.
--- 
+--
 
 
 create table Room (
@@ -116,10 +116,10 @@ create unique index PHone_name on PHone using btree (slotname bpchar_ops);
 
 
 -- ************************************************************
--- * 
+-- *
 -- * Trigger procedures and functions for the patchfield
 -- * test of PL/pgSQL
--- * 
+-- *
 -- ************************************************************
 
 
@@ -708,11 +708,11 @@ begin
     mytype := substr(myname, 1, 2);
     link := mytype || substr(blname, 1, 2);
     if link = ''PLPL'' then
-        raise exception 
+        raise exception
                ''backlink between two phone lines does not make sense'';
     end if;
     if link in (''PLWS'', ''WSPL'') then
-        raise exception 
+        raise exception
                ''direct link of phone line to wall slot not permitted'';
     end if;
     if mytype = ''PS'' then
@@ -868,19 +868,19 @@ begin
     mytype := substr(myname, 1, 2);
     link := mytype || substr(blname, 1, 2);
     if link = ''PHPH'' then
-        raise exception 
+        raise exception
                ''slotlink between two phones does not make sense'';
     end if;
     if link in (''PHHS'', ''HSPH'') then
-        raise exception 
+        raise exception
                ''link of phone to hub does not make sense'';
     end if;
     if link in (''PHIF'', ''IFPH'') then
-        raise exception 
+        raise exception
                ''link of phone to hub does not make sense'';
     end if;
     if link in (''PSWS'', ''WSPS'') then
-        raise exception 
+        raise exception
                ''slotlink from patchslot to wallslot not permitted'';
     end if;
     if mytype = ''PS'' then
@@ -2444,7 +2444,7 @@ drop function footest();
 -- test scrollable cursor support
 
 create function sc_test() returns setof integer as $$
-declare 
+declare
   c scroll cursor for select f1 from int4_tbl;
   x integer;
 begin
@@ -2461,7 +2461,7 @@ $$ language plpgsql;
 select * from sc_test();
 
 create or replace function sc_test() returns setof integer as $$
-declare 
+declare
   c no scroll cursor for select f1 from int4_tbl;
   x integer;
 begin
@@ -2478,7 +2478,7 @@ $$ language plpgsql;
 select * from sc_test();  -- fails because of NO SCROLL specification
 
 create or replace function sc_test() returns setof integer as $$
-declare 
+declare
   c refcursor;
   x integer;
 begin
@@ -2495,7 +2495,7 @@ $$ language plpgsql;
 select * from sc_test();
 
 create or replace function sc_test() returns setof integer as $$
-declare 
+declare
   c refcursor;
   x integer;
 begin
@@ -2688,9 +2688,9 @@ drop function return_dquery();
 
 create or replace function raise_test() returns void as $$
 begin
-  raise notice '% % %', 1, 2, 3 
+  raise notice '% % %', 1, 2, 3
      using errcode = '55001', detail = 'some detail info', hint = 'some hint';
-  raise '% % %', 1, 2, 3 
+  raise '% % %', 1, 2, 3
      using errcode = 'division_by_zero', detail = 'some detail info';
 end;
 $$ language plpgsql;
@@ -2812,3 +2812,69 @@ $$ language plpgsql;
 select raise_test();
 
 drop function raise_test();
+
+-- test CASE statement
+
+create or replace function case_test(bigint) returns text as $$
+declare a int = 10;
+        b int = 1;
+begin
+  case $1
+    when 1 then
+      return 'one';
+    when 2 then
+      return 'two';
+    when 3,4,3+5 then
+      return 'three, four or eight';
+    when a then
+      return 'ten';
+    when a+b, a+b+1 then
+      return 'eleven, twelve';
+  end case;
+end;
+$$ language plpgsql immutable;
+
+select case_test(1);
+select case_test(2);
+select case_test(3);
+select case_test(4);
+select case_test(5); -- fails
+select case_test(8);
+select case_test(10);
+select case_test(11);
+select case_test(12);
+select case_test(13); -- fails
+
+create or replace function catch() returns void as $$
+begin
+  raise notice '%', case_test(6);
+exception
+  when case_not_found then
+    raise notice 'caught case_not_found % %', SQLSTATE, SQLERRM;
+end
+$$ language plpgsql;
+
+select catch();
+
+-- test the searched variant too, as well as ELSE
+create or replace function case_test(bigint) returns text as $$
+declare a int = 10;
+begin
+  case
+    when $1 = 1 then
+      return 'one';
+    when $1 = a + 2 then
+      return 'twelve';
+    else
+      return 'other';
+  end case;
+end;
+$$ language plpgsql immutable;
+
+select case_test(1);
+select case_test(2);
+select case_test(12);
+select case_test(13);
+
+drop function catch();
+drop function case_test(bigint);