Support SQL/PSM-compatible CASE statement in plpgsql.
authorTom Lane
Thu, 15 May 2008 22:39:49 +0000 (22:39 +0000)
committerTom Lane
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 @@
-
+
 
 
  <productname>PostgreSQL</productname> Error Codes
 
  
   
-  stant">
-  stant" spanname="span13">
+  dname">
+  dname" spanname="span13">
 
   
    
     Error Code
     Meaning
-    Constant
+    Condition Name
    
   
 
 
 
 
+
+Class 20 — Case Not Found
+
+
+
+20000
+CASE NOT FOUND
+case_not_found
+
+
+
 
 Class 21 — Cardinality Violation
 
index 09ad6944dba5a065745f46346149e3084a4e6121..d2853d2d901b2644b40963c22eb34dd6d2ce7d12 100644 (file)
@@ -1,4 +1,4 @@
-
+
 
 
   <application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language
@@ -1581,9 +1581,9 @@ SELECT * FROM getallfoo();
     Conditionals
 
     
-     <literal>IF statements let you execute commands based on
-     certain conditions.  PL/pgSQL has five forms of
-     <literal>IF:
+     <command>IF and CASE statements let you execute
+     alternative commands based on certain conditions.
+     <application>PL/pgSQL has five forms of >IF:
     
      
       IF ... THEN
@@ -1601,6 +1601,22 @@ SELECT * FROM getallfoo();
       IF ... THEN ... ELSEIF ... THEN ... ELSE
      
     
+
+    and four forms of CASE:
+    
+     
+      CASE ... WHEN ... THEN ... END CASE
+     
+     
+      CASE ... WHEN ... THEN ... ELSE ... END CASE
+     
+     
+      CASE WHEN ... THEN ... END CASE
+     
+     
+      CASE WHEN ... THEN ... ELSE ... END CASE
+     
+    
     
 
     
@@ -1751,6 +1767,93 @@ END IF;
        ELSEIF is an alias for ELSIF.
       
      
+
+     
+      Simple <literal>CASE</>
+
+
+CASE search-expression
+    WHEN expression expression  ...  THEN
+      statements
+   WHEN expression expression  ...  THEN
+      statements
+    ... 
+   ELSE
+      statements 
+END CASE;
+
+
+      
+       The simple form of CASE provides conditional execution
+       based on equality of operands.  The search-expression
+       is evaluated (once) and successively compared to each
+       expression in the WHEN clauses.
+       If a match is found, then the corresponding
+       statements are executed, and then control
+       passes to the next statement after END CASE.  (Subsequent
+       WHEN expressions are not evaluated.)  If no match is
+       found, the ELSE statements are
+       executed; but if ELSE is not present, then a
+       CASE_NOT_FOUND exception is raised.
+      
+
+      
+       Here is a simple example:
+
+
+CASE x
+    WHEN 1, 2 THEN
+        msg := 'one or two';
+    ELSE
+        msg := 'other value than one or two';
+END CASE;
+
+      
+     
+
+     
+      Searched <literal>CASE</>
+
+
+CASE
+    WHEN boolean-expression THEN
+      statements
+   WHEN boolean-expression THEN
+      statements
+    ... 
+   ELSE
+      statements 
+END CASE;
+
+
+      
+       The searched form of CASE provides conditional execution
+       based on truth of boolean expressions.  Each WHEN clause's
+       boolean-expression is evaluated in turn,
+       until one is found that yields true.  Then the
+       corresponding statements are executed, and
+       then control passes to the next statement after END CASE.
+       (Subsequent WHEN expressions are not evaluated.)
+       If no true result is found, the ELSE
+       statements are executed;
+       but if ELSE is not present, then a
+       CASE_NOT_FOUND exception is raised.
+      
+
+      
+       Here is an example:
+
+
+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;
+
+      
+
+     
    
 
    
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  decl_sect
@@ -116,7 +119,7 @@ static List             *read_raise_options(void);
 %type         decl_stmts decl_stmt
 
 %type    expr_until_semi expr_until_rightbracket
-%type    expr_until_then expr_until_loop
+%type    expr_until_then expr_until_loop opt_expr_until_when
 %type    opt_exitcond
 
 %type    assign_var
@@ -135,12 +138,16 @@ static List               *read_raise_options(void);
 %type    stmt_return stmt_raise stmt_execsql stmt_execsql_insert
 %type    stmt_dynexecute stmt_for stmt_perform stmt_getdiag
 %type    stmt_open stmt_fetch stmt_move stmt_close stmt_null
+%type    stmt_case
 
 %type    proc_exceptions
 %type  exception_sect
 %type   proc_exception
 %type   proc_conditions proc_condition
 
+%type    case_when
+%type    case_when_list opt_case_else
+
 %type    getdiag_list
 %type  getdiag_list_item
 %type    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);