Improve plpgsql's RAISE command. It is now possible to attach DETAIL and
authorTom Lane
Tue, 13 May 2008 22:10:30 +0000 (22:10 +0000)
committerTom Lane
Tue, 13 May 2008 22:10:30 +0000 (22:10 +0000)
HINT fields to a user-thrown error message, and to specify the SQLSTATE
error code to use.  The syntax has also been tweaked so that the
Oracle-compatible case "RAISE exception_name" works (though you won't get a
very nice error message if you just write that much).  Lastly, support
the Oracle-compatible syntax "RAISE" with no parameters to re-throw
the current error from within an EXCEPTION block.

In passing, allow the syntax SQLSTATE 'nnnnn' within EXCEPTION lists,
so that there is a way to trap errors with custom SQLSTATE codes.

Pavel Stehule and Tom Lane

doc/src/sgml/plpgsql.sgml
src/pl/plpgsql/src/gram.y
src/pl/plpgsql/src/pl_comp.c
src/pl/plpgsql/src/pl_exec.c
src/pl/plpgsql/src/pl_funcs.c
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 1065eddc74efb52c270bee0a89c0e5b9d4e9045f..09ad6944dba5a065745f46346149e3084a4e6121 100644 (file)
@@ -1,4 +1,4 @@
-
+
 
 
   <application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language
@@ -2133,7 +2133,12 @@ END;
      condition name OTHERS matches every error type except
      QUERY_CANCELED.  (It is possible, but often unwise,
      to trap QUERY_CANCELED by name.)  Condition names are
-     not case-sensitive.
+     not case-sensitive.  Also, an error condition can be specified
+     by SQLSTATE code; for example these are equivalent:
+
+        WHEN division_by_zero THEN ...
+        WHEN SQLSTATE '22012' THEN ...
+
     
 
     
@@ -2750,13 +2755,19 @@ END LOOP  label ;
     raise errors.
 
 
-RAISE level 'format' expression , ...;
+RAISE  level  'format' expression , ...  USING option = expression , ...  ;
+RAISE  level  condition_name  USING option = expression , ...  ;
+RAISE  level  SQLSTATE 'sqlstate'  USING option = expression , ...  ;
+RAISE  level  USING option = expression , ... ;
+RAISE ;
 
 
-    Possible levels are DEBUG,
+    The level option specifies
+    the error severity.  Allowed levels are DEBUG,
     LOGINFO,
     NOTICEWARNING,
-    and EXCEPTION.
+    and EXCEPTION, with EXCEPTION
+    being the default.
     EXCEPTION raises an error (which normally aborts the
     current transaction); the other levels only generate messages of different
     priority levels.
@@ -2769,19 +2780,17 @@ RAISE level '
    
 
    
+    After level if any,
+    you can write a format
+    (which must be a simple string literal, not an expression).  The
+    format string specifies the error message text to be reported.
+    The format string can be followed
+    by optional argument expressions to be inserted into the message.
     Inside the format string, % is replaced by the
-    next optional argument's string representation. Write
+    string representation of the next optional argument's value. Write
     %% to emit a literal %.
-    Arguments can be simple variables or expressions,
-    but the format must be a simple string literal.
    
 
-   
-
    
     In this example, the value of v_job_id will replace the
     % in the string:
@@ -2791,19 +2800,90 @@ RAISE NOTICE 'Calling cs_create_job(%)', v_job_id;
    
 
    
-    This example will abort the transaction with the given error message:
+    You can attach additional information to the error report by writing
+    USING followed by 
+    class="parameter">option = 
+    class="parameter">expression items.  The allowed
+    option keywords are
+    MESSAGE, DETAIL, HINT, and
+    ERRCODE, while each 
+    class="parameter">expression can be any string-valued
+    expression.
+    MESSAGE sets the error message text (this option can't
+    be used in the form of RAISE that includes a format
+    string before USING).
+    DETAIL supplies an error detail message, while
+    HINT supplies a hint message.
+    ERRCODE specifies the error code (SQLSTATE) to report,
+    either by condition name as shown in ,
+    or directly as a five-character SQLSTATE code.
+   
+
+   
+    This example will abort the transaction with the given error message
+    and hint:
+
+RAISE EXCEPTION 'Nonexistent ID --> %', user_id USING HINT = 'Please check your user id';
+
+   
+
+   
+    These two examples show equivalent ways of setting the SQLSTATE:
+
+RAISE 'Duplicate user ID: %', user_id USING ERRCODE = 'unique_violation';
+RAISE 'Duplicate user ID: %', user_id USING ERRCODE = '23505';
+
+   
+
+   
+    There is a second RAISE syntax in which the main argument
+    is the condition name or SQLSTATE to be reported, for example:
+
+RAISE division_by_zero;
+RAISE SQLSTATE '22012';
+
+    In this syntax, USING can be used to supply a custom
+    error message, detail, or hint.  Another way to do the earlier
+    example is
 
-RAISE EXCEPTION 'Nonexistent ID --> %', user_id;
+RAISE unique_violation USING MESSAGE = 'Duplicate user ID: ' || user_id;
 
    
 
+   
+    Still another variant is to write RAISE USING or RAISE
+    level USING and put
+    everything else into the USING list.
+   
+
+   
+    The last variant of RAISE has no parameters at all.
+    This form can only be used inside a BEGIN block's
+    EXCEPTION clause;
+    it causes the error currently being handled to be re-thrown to the
+    next enclosing block.
+   
+
+   
+    If no condition name nor SQLSTATE is specified in a
+    RAISE EXCEPTION command, the default is to use
+    RAISE_EXCEPTION (P0001).  If no message
+    text is specified, the default is to use the condition name or
+    SQLSTATE as message text.
+   
+
+   
     
-     RAISE EXCEPTION presently always generates
-     the same SQLSTATE code, P0001, no matter what message
-     it is invoked with.  It is possible to trap this exception with
-     EXCEPTION ... WHEN RAISE_EXCEPTION THEN ... but there
-     is no way to tell one RAISE from another.
+     When specifying an error code by SQLSTATE code, you are not
+     limited to the predefined error codes, but can select any
+     error code consisting of five digits and/or upper-case ASCII
+     letters, other than 00000.  It is recommended that
+     you avoid throwing error codes that end in three zeroes, because
+     these are category codes and can only be trapped by trapping
+     the whole category.
     
+   
+
  
 
  
@@ -4307,7 +4387,9 @@ $$ LANGUAGE plpgsql;
      
       
        The syntax of RAISE is considerably different from
-       Oracle's similar statement.
+       Oracle's statement, although the basic case RAISE
+       exception_name works
+       similarly.
       
      
      
index 979ebee3ca330a5de962c0ebedc0540db67d6346..b67a8bba776a28a493401ce233ee038e70678a4c 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.111 2008/05/03 00:11:36 tgl Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.112 2008/05/13 22:10:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -52,6 +52,7 @@ static    void             check_labels(const char *start_label,
                                      const char *end_label);
 static PLpgSQL_expr    *read_cursor_args(PLpgSQL_var *cursor,
                                          int until, const char *expected);
+static List                *read_raise_options(void);
 
 %}
 
@@ -138,11 +139,7 @@ static PLpgSQL_expr    *read_cursor_args(PLpgSQL_var *cursor,
 %type    proc_exceptions
 %type  exception_sect
 %type   proc_exception
-%type   proc_conditions
-
-
-%type    raise_level
-%type         raise_msg
+%type   proc_conditions proc_condition
 
 %type    getdiag_list
 %type  getdiag_list_item
@@ -164,7 +161,6 @@ static PLpgSQL_expr     *read_cursor_args(PLpgSQL_var *cursor,
 %token K_CONSTANT
 %token K_CONTINUE
 %token K_CURSOR
-%token K_DEBUG
 %token K_DECLARE
 %token K_DEFAULT
 %token K_DIAGNOSTICS
@@ -181,16 +177,13 @@ static PLpgSQL_expr   *read_cursor_args(PLpgSQL_var *cursor,
 %token K_GET
 %token K_IF
 %token K_IN
-%token K_INFO
 %token K_INSERT
 %token K_INTO
 %token K_IS
-%token K_LOG
 %token K_LOOP
 %token K_MOVE
 %token K_NOSCROLL
 %token K_NOT
-%token K_NOTICE
 %token K_NULL
 %token K_OPEN
 %token K_OR
@@ -207,7 +200,6 @@ static PLpgSQL_expr     *read_cursor_args(PLpgSQL_var *cursor,
 %token K_TO
 %token K_TYPE
 %token K_USING
-%token K_WARNING
 %token K_WHEN
 %token K_WHILE
 
@@ -1246,7 +1238,7 @@ stmt_return       : K_RETURN lno
                    }
                ;
 
-stmt_raise     : K_RAISE lno raise_level raise_msg
+stmt_raise     : K_RAISE lno
                    {
                        PLpgSQL_stmt_raise      *new;
                        int tok;
@@ -1255,66 +1247,130 @@ stmt_raise     : K_RAISE lno raise_level raise_msg
 
                        new->cmd_type   = PLPGSQL_STMT_RAISE;
                        new->lineno     = $2;
-                       new->elog_level = $3;
-                       new->message    = $4;
+                       new->elog_level = ERROR;    /* default */
+                       new->condname   = NULL;
+                       new->message    = NULL;
                        new->params     = NIL;
+                       new->options    = NIL;
 
                        tok = yylex();
+                       if (tok == 0)
+                           yyerror("unexpected end of function definition");
 
                        /*
-                        * We expect either a semi-colon, which
-                        * indicates no parameters, or a comma that
-                        * begins the list of parameter expressions
+                        * We could have just RAISE, meaning to re-throw
+                        * the current error.
                         */
-                       if (tok != ',' && tok != ';')
-                           yyerror("syntax error");
-
-                       if (tok == ',')
+                       if (tok != ';')
                        {
-                           do
+                           /*
+                            * First is an optional elog severity level.
+                            * Most of these are not plpgsql keywords,
+                            * so we rely on examining yytext.
+                            */
+                           if (pg_strcasecmp(yytext, "exception") == 0)
+                           {
+                               new->elog_level = ERROR;
+                               tok = yylex();
+                           }
+                           else if (pg_strcasecmp(yytext, "warning") == 0)
+                           {
+                               new->elog_level = WARNING;
+                               tok = yylex();
+                           }
+                           else if (pg_strcasecmp(yytext, "notice") == 0)
+                           {
+                               new->elog_level = NOTICE;
+                               tok = yylex();
+                           }
+                           else if (pg_strcasecmp(yytext, "info") == 0)
+                           {
+                               new->elog_level = INFO;
+                               tok = yylex();
+                           }
+                           else if (pg_strcasecmp(yytext, "log") == 0)
+                           {
+                               new->elog_level = LOG;
+                               tok = yylex();
+                           }
+                           else if (pg_strcasecmp(yytext, "debug") == 0)
                            {
-                               PLpgSQL_expr *expr;
+                               new->elog_level = DEBUG1;
+                               tok = yylex();
+                           }
+                           if (tok == 0)
+                               yyerror("unexpected end of function definition");
 
-                               expr = read_sql_expression2(',', ';',
-                                                           ", or ;",
-                                                           &tok);
-                               new->params = lappend(new->params, expr);
-                           } while (tok == ',');
-                       }
+                           /*
+                            * Next we can have a condition name, or
+                            * equivalently SQLSTATE 'xxxxx', or a string
+                            * literal that is the old-style message format,
+                            * or USING to start the option list immediately.
+                            */
+                           if (tok == T_STRING)
+                           {
+                               /* old style message and parameters */
+                               new->message = plpgsql_get_string_value();
+                               /*
+                                * We expect either a semi-colon, which
+                                * indicates no parameters, or a comma that
+                                * begins the list of parameter expressions,
+                                * or USING to begin the options list.
+                                */
+                               tok = yylex();
+                               if (tok != ',' && tok != ';' && tok != K_USING)
+                                   yyerror("syntax error");
 
-                       $$ = (PLpgSQL_stmt *)new;
-                   }
-               ;
+                               while (tok == ',')
+                               {
+                                   PLpgSQL_expr *expr;
 
-raise_msg      : T_STRING
-                   {
-                       $$ = plpgsql_get_string_value();
-                   }
-               ;
+                                   expr = read_sql_construct(',', ';', K_USING,
+                                                             ", or ; or USING",
+                                                             "SELECT ",
+                                                             true, true, &tok);
+                                   new->params = lappend(new->params, expr);
+                               }
+                           }
+                           else if (tok != K_USING)
+                           {
+                               /* must be condition name or SQLSTATE */
+                               if (pg_strcasecmp(yytext, "sqlstate") == 0)
+                               {
+                                   /* next token should be a string literal */
+                                   char   *sqlstatestr;
+
+                                   if (yylex() != T_STRING)
+                                       yyerror("syntax error");
+                                   sqlstatestr = plpgsql_get_string_value();
+
+                                   if (strlen(sqlstatestr) != 5)
+                                       yyerror("invalid SQLSTATE code");
+                                   if (strspn(sqlstatestr, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5)
+                                       yyerror("invalid SQLSTATE code");
+                                   new->condname = sqlstatestr;
+                               }
+                               else
+                               {
+                                   char   *cname;
+
+                                   if (tok != T_WORD)
+                                       yyerror("syntax error");
+                                   plpgsql_convert_ident(yytext, &cname, 1);
+                                   plpgsql_recognize_err_condition(cname,
+                                                                   false);
+                                   new->condname = cname;
+                               }
+                               tok = yylex();
+                               if (tok != ';' && tok != K_USING)
+                                   yyerror("syntax error");
+                           }
 
-raise_level        : K_EXCEPTION
-                   {
-                       $$ = ERROR;
-                   }
-               | K_WARNING
-                   {
-                       $$ = WARNING;
-                   }
-               | K_NOTICE
-                   {
-                       $$ = NOTICE;
-                   }
-               | K_INFO
-                   {
-                       $$ = INFO;
-                   }
-               | K_LOG
-                   {
-                       $$ = LOG;
-                   }
-               | K_DEBUG
-                   {
-                       $$ = DEBUG1;
+                           if (tok == K_USING)
+                               new->options = read_raise_options();
+                       }
+
+                       $$ = (PLpgSQL_stmt *)new;
                    }
                ;
 
@@ -1592,20 +1648,61 @@ proc_exception  : K_WHEN lno proc_conditions K_THEN proc_sect
                    }
                ;
 
-proc_conditions    : proc_conditions K_OR opt_lblname
+proc_conditions    : proc_conditions K_OR proc_condition
                        {
                            PLpgSQL_condition   *old;
 
                            for (old = $1; old->next != NULL; old = old->next)
                                /* skip */ ;
-                           old->next = plpgsql_parse_err_condition($3);
-
+                           old->next = $3;
                            $$ = $1;
                        }
-               | opt_lblname
+               | proc_condition
+                       {
+                           $$ = $1;
+                       }
+               ;
+
+proc_condition : opt_lblname
                        {
                            $$ = plpgsql_parse_err_condition($1);
                        }
+               | T_SCALAR
+                       {
+                           /*
+                            * Because we know the special sqlstate variable
+                            * is at the top of the namestack (see the
+                            * exception_sect production), the SQLSTATE
+                            * keyword will always lex as T_SCALAR.  This
+                            * might not be true in other parsing contexts!
+                            */
+                           PLpgSQL_condition *new;
+                           char   *sqlstatestr;
+
+                           if (pg_strcasecmp(yytext, "sqlstate") != 0)
+                               yyerror("syntax error");
+
+                           /* next token should be a string literal */
+                           if (yylex() != T_STRING)
+                               yyerror("syntax error");
+                           sqlstatestr = plpgsql_get_string_value();
+
+                           if (strlen(sqlstatestr) != 5)
+                               yyerror("invalid SQLSTATE code");
+                           if (strspn(sqlstatestr, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5)
+                               yyerror("invalid SQLSTATE code");
+
+                           new = palloc(sizeof(PLpgSQL_condition));
+                           new->sqlerrstate = MAKE_SQLSTATE(sqlstatestr[0],
+                                                            sqlstatestr[1],
+                                                            sqlstatestr[2],
+                                                            sqlstatestr[3],
+                                                            sqlstatestr[4]);
+                           new->condname = sqlstatestr;
+                           new->next = NULL;
+
+                           $$ = new;
+                       }
                ;
 
 expr_until_semi :
@@ -2658,6 +2755,55 @@ read_cursor_args(PLpgSQL_var *cursor, int until, const char *expected)
    return expr;
 }
 
+/*
+ * Parse RAISE ... USING options
+ */
+static List *
+read_raise_options(void)
+{
+   List       *result = NIL;
+
+   for (;;)
+   {
+       PLpgSQL_raise_option *opt;
+       int     tok;
+
+       if ((tok = yylex()) == 0)
+           yyerror("unexpected end of function definition");
+
+       opt = (PLpgSQL_raise_option *) palloc(sizeof(PLpgSQL_raise_option));
+
+       if (pg_strcasecmp(yytext, "errcode") == 0)
+           opt->opt_type = PLPGSQL_RAISEOPTION_ERRCODE;
+       else if (pg_strcasecmp(yytext, "message") == 0)
+           opt->opt_type = PLPGSQL_RAISEOPTION_MESSAGE;
+       else if (pg_strcasecmp(yytext, "detail") == 0)
+           opt->opt_type = PLPGSQL_RAISEOPTION_DETAIL;
+       else if (pg_strcasecmp(yytext, "hint") == 0)
+           opt->opt_type = PLPGSQL_RAISEOPTION_HINT;
+       else
+       {
+           plpgsql_error_lineno = plpgsql_scanner_lineno();
+           ereport(ERROR,
+                   (errcode(ERRCODE_SYNTAX_ERROR),
+                    errmsg("unrecognized RAISE statement option \"%s\"",
+                           yytext)));
+       }
+
+       if (yylex() != K_ASSIGN)
+           yyerror("syntax error, expected \"=\"");
+
+       opt->expr = read_sql_expression2(',', ';', ", or ;", &tok);
+
+       result = lappend(result, opt);
+
+       if (tok == ';')
+           break;
+   }
+
+   return result;
+}
+
 
 /* Needed to avoid conflict between different prefix settings: */
 #undef yylex
index 2252618ad887896b10811f797307607bad4a479c..737bac5888119ab1d4adc1f303120fabcda6c76f 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.125 2008/05/12 00:00:54 alvherre Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.126 2008/05/13 22:10:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1749,6 +1749,42 @@ build_datatype(HeapTuple typeTup, int32 typmod)
    return typ;
 }
 
+/*
+ *  plpgsql_recognize_err_condition
+ *         Check condition name and translate it to SQLSTATE.
+ *
+ * Note: there are some cases where the same condition name has multiple
+ * entries in the table.  We arbitrarily return the first match.
+ */
+int
+plpgsql_recognize_err_condition(const char *condname, bool allow_sqlstate)
+{
+   int         i;
+
+   if (allow_sqlstate)
+   {
+       if (strlen(condname) == 5 &&
+           strspn(condname, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 5)
+           return MAKE_SQLSTATE(condname[0],
+                                condname[1],
+                                condname[2],
+                                condname[3],
+                                condname[4]);
+   }
+
+   for (i = 0; exception_label_map[i].label != NULL; i++)
+   {
+       if (strcmp(condname, exception_label_map[i].label) == 0)
+           return exception_label_map[i].sqlerrstate;
+   }
+
+   ereport(ERROR,
+           (errcode(ERRCODE_UNDEFINED_OBJECT),
+            errmsg("unrecognized exception condition \"%s\"",
+                   condname)));
+   return 0;                   /* keep compiler quiet */
+}
+
 /*
  * plpgsql_parse_err_condition
  *     Generate PLpgSQL_condition entry(s) for an exception condition name
index b248384af4162817f1984714ca4c7b3ae326dcdd..2ba45befb71e899473ec68a39918e8a119fcec90 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.213 2008/05/12 20:02:02 alvherre Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.214 2008/05/13 22:10:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -316,13 +316,17 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
        estate.err_text = NULL;
 
        /*
-        * Provide a more helpful message if a CONTINUE has been used outside
-        * a loop.
+        * Provide a more helpful message if a CONTINUE or RAISE has been used
+        * outside the context it can work in.
         */
        if (rc == PLPGSQL_RC_CONTINUE)
            ereport(ERROR,
                    (errcode(ERRCODE_SYNTAX_ERROR),
                     errmsg("CONTINUE cannot be used outside a loop")));
+       else if (rc == PLPGSQL_RC_RERAISE)
+           ereport(ERROR,
+                   (errcode(ERRCODE_SYNTAX_ERROR),
+                    errmsg("RAISE without parameters cannot be used outside an exception handler")));
        else
            ereport(ERROR,
               (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
@@ -662,13 +666,17 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
        estate.err_text = NULL;
 
        /*
-        * Provide a more helpful message if a CONTINUE has been used outside
-        * a loop.
+        * Provide a more helpful message if a CONTINUE or RAISE has been used
+        * outside the context it can work in.
         */
        if (rc == PLPGSQL_RC_CONTINUE)
            ereport(ERROR,
                    (errcode(ERRCODE_SYNTAX_ERROR),
                     errmsg("CONTINUE cannot be used outside a loop")));
+       else if (rc == PLPGSQL_RC_RERAISE)
+           ereport(ERROR,
+                   (errcode(ERRCODE_SYNTAX_ERROR),
+                    errmsg("RAISE without parameters cannot be used outside an exception handler")));
        else
            ereport(ERROR,
               (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
@@ -1109,6 +1117,11 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
                    free_var(errm_var);
                    errm_var->value = (Datum) 0;
                    errm_var->isnull = true;
+
+                   /* re-throw error if requested by handler */
+                   if (rc == PLPGSQL_RC_RERAISE)
+                       ReThrowError(edata);
+
                    break;
                }
            }
@@ -1139,8 +1152,9 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
    switch (rc)
    {
        case PLPGSQL_RC_OK:
-       case PLPGSQL_RC_CONTINUE:
        case PLPGSQL_RC_RETURN:
+       case PLPGSQL_RC_CONTINUE:
+       case PLPGSQL_RC_RERAISE:
            return rc;
 
        case PLPGSQL_RC_EXIT:
@@ -1469,7 +1483,8 @@ exec_stmt_loop(PLpgSQL_execstate *estate, PLpgSQL_stmt_loop *stmt)
                break;
 
            case PLPGSQL_RC_RETURN:
-               return PLPGSQL_RC_RETURN;
+           case PLPGSQL_RC_RERAISE:
+               return rc;
 
            default:
                elog(ERROR, "unrecognized rc: %d", rc);
@@ -1532,7 +1547,8 @@ exec_stmt_while(PLpgSQL_execstate *estate, PLpgSQL_stmt_while *stmt)
                break;
 
            case PLPGSQL_RC_RETURN:
-               return PLPGSQL_RC_RETURN;
+           case PLPGSQL_RC_RERAISE:
+               return rc;
 
            default:
                elog(ERROR, "unrecognized rc: %d", rc);
@@ -1650,8 +1666,9 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
         */
        rc = exec_stmts(estate, stmt->body);
 
-       if (rc == PLPGSQL_RC_RETURN)
-           break;              /* return from function */
+       if (rc == PLPGSQL_RC_RETURN ||
+           rc == PLPGSQL_RC_RERAISE)
+           break;              /* break out of the loop */
        else if (rc == PLPGSQL_RC_EXIT)
        {
            if (estate->exitlabel == NULL)
@@ -2267,64 +2284,163 @@ exec_init_tuple_store(PLpgSQL_execstate *estate)
 static int
 exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
 {
-   char       *cp;
-   PLpgSQL_dstring ds;
-   ListCell   *current_param;
+   int         err_code = 0;
+   char       *condname = NULL;
+   char       *err_message = NULL;
+   char       *err_detail = NULL;
+   char       *err_hint = NULL;
+   ListCell   *lc;
 
-   plpgsql_dstring_init(&ds);
-   current_param = list_head(stmt->params);
+   /* RAISE with no parameters: re-throw current exception */
+   if (stmt->condname == NULL && stmt->message == NULL &&
+       stmt->options == NIL)
+       return PLPGSQL_RC_RERAISE;
 
-   for (cp = stmt->message; *cp; cp++)
+   if (stmt->condname)
    {
-       /*
-        * Occurrences of a single % are replaced by the next parameter's
-        * external representation. Double %'s are converted to one %.
-        */
-       if (cp[0] == '%')
-       {
-           Oid         paramtypeid;
-           Datum       paramvalue;
-           bool        paramisnull;
-           char       *extval;
+       err_code = plpgsql_recognize_err_condition(stmt->condname, true);
+       condname = pstrdup(stmt->condname);
+   }
+
+   if (stmt->message)
+   {
+       PLpgSQL_dstring ds;
+       ListCell   *current_param;
+       char       *cp;
+
+       plpgsql_dstring_init(&ds);
+       current_param = list_head(stmt->params);
 
-           if (cp[1] == '%')
+       for (cp = stmt->message; *cp; cp++)
+       {
+           /*
+            * Occurrences of a single % are replaced by the next parameter's
+            * external representation. Double %'s are converted to one %.
+            */
+           if (cp[0] == '%')
            {
-               plpgsql_dstring_append_char(&ds, cp[1]);
-               cp++;
-               continue;
-           }
+               Oid         paramtypeid;
+               Datum       paramvalue;
+               bool        paramisnull;
+               char       *extval;
 
-           if (current_param == NULL)
-               ereport(ERROR,
-                       (errcode(ERRCODE_SYNTAX_ERROR),
-                        errmsg("too few parameters specified for RAISE")));
+               if (cp[1] == '%')
+               {
+                   plpgsql_dstring_append_char(&ds, cp[1]);
+                   cp++;
+                   continue;
+               }
 
-           paramvalue = exec_eval_expr(estate,
-                                     (PLpgSQL_expr *) lfirst(current_param),
-                                       ¶misnull,
-                                       ¶mtypeid);
+               if (current_param == NULL)
+                   ereport(ERROR,
+                           (errcode(ERRCODE_SYNTAX_ERROR),
+                            errmsg("too few parameters specified for RAISE")));
+
+               paramvalue = exec_eval_expr(estate,
+                                           (PLpgSQL_expr *) lfirst(current_param),
+                                           ¶misnull,
+                                           ¶mtypeid);
 
-           if (paramisnull)
-               extval = "";
+               if (paramisnull)
+                   extval = "";
+               else
+                   extval = convert_value_to_string(paramvalue, paramtypeid);
+               plpgsql_dstring_append(&ds, extval);
+               current_param = lnext(current_param);
+               exec_eval_cleanup(estate);
+           }
            else
-               extval = convert_value_to_string(paramvalue, paramtypeid);
-           plpgsql_dstring_append(&ds, extval);
-           current_param = lnext(current_param);
-           exec_eval_cleanup(estate);
-           continue;
+               plpgsql_dstring_append_char(&ds, cp[0]);
        }
 
-       plpgsql_dstring_append_char(&ds, cp[0]);
+       /*
+        * If more parameters were specified than were required to process the
+        * format string, throw an error
+        */
+       if (current_param != NULL)
+           ereport(ERROR,
+                   (errcode(ERRCODE_SYNTAX_ERROR),
+                    errmsg("too many parameters specified for RAISE")));
+
+       err_message = plpgsql_dstring_get(&ds);
+       /* No dstring_free here, the pfree(err_message) does it */
    }
 
-   /*
-    * If more parameters were specified than were required to process the
-    * format string, throw an error
-    */
-   if (current_param != NULL)
-       ereport(ERROR,
-               (errcode(ERRCODE_SYNTAX_ERROR),
-                errmsg("too many parameters specified for RAISE")));
+   foreach(lc, stmt->options)
+   {
+       PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(lc);
+       Datum       optionvalue;
+       bool        optionisnull;
+       Oid         optiontypeid;
+       char       *extval;
+
+       optionvalue = exec_eval_expr(estate, opt->expr,
+                                    &optionisnull,
+                                    &optiontypeid);
+       if (optionisnull)
+           ereport(ERROR,
+                   (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                    errmsg("RAISE statement option cannot be NULL")));
+
+       extval = convert_value_to_string(optionvalue, optiontypeid);
+
+       switch (opt->opt_type)
+       {
+           case PLPGSQL_RAISEOPTION_ERRCODE:
+               if (err_code)
+                   ereport(ERROR,
+                           (errcode(ERRCODE_SYNTAX_ERROR),
+                            errmsg("RAISE option already specified: %s",
+                                   "ERRCODE")));
+               err_code = plpgsql_recognize_err_condition(extval, true);
+               condname = pstrdup(extval);
+               break;
+           case PLPGSQL_RAISEOPTION_MESSAGE:
+               if (err_message)
+                   ereport(ERROR,
+                           (errcode(ERRCODE_SYNTAX_ERROR),
+                            errmsg("RAISE option already specified: %s",
+                                   "MESSAGE")));
+               err_message = pstrdup(extval);
+               break;
+           case PLPGSQL_RAISEOPTION_DETAIL:
+               if (err_detail)
+                   ereport(ERROR,
+                           (errcode(ERRCODE_SYNTAX_ERROR),
+                            errmsg("RAISE option already specified: %s",
+                                   "DETAIL")));
+               err_detail = pstrdup(extval);
+               break;
+           case PLPGSQL_RAISEOPTION_HINT:
+               if (err_hint)
+                   ereport(ERROR,
+                           (errcode(ERRCODE_SYNTAX_ERROR),
+                            errmsg("RAISE option already specified: %s",
+                                   "HINT")));
+               err_hint = pstrdup(extval);
+               break;
+           default:
+               elog(ERROR, "unrecognized raise option: %d", opt->opt_type);
+       }
+
+       exec_eval_cleanup(estate);
+   }
+
+   /* Default code if nothing specified */
+   if (err_code == 0 && stmt->elog_level >= ERROR)
+       err_code = ERRCODE_RAISE_EXCEPTION;
+
+   /* Default error message if nothing specified */
+   if (err_message == NULL)
+   {
+       if (condname)
+       {
+           err_message = condname;
+           condname = NULL;
+       }
+       else
+           err_message = pstrdup(unpack_sql_state(err_code));
+   }
 
    /*
     * Throw the error (may or may not come back)
@@ -2332,12 +2448,21 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
    estate->err_text = raise_skip_msg;  /* suppress traceback of raise */
 
    ereport(stmt->elog_level,
-        ((stmt->elog_level >= ERROR) ? errcode(ERRCODE_RAISE_EXCEPTION) : 0,
-         errmsg_internal("%s", plpgsql_dstring_get(&ds))));
+           (err_code ? errcode(err_code) : 0,
+            errmsg_internal("%s", err_message),
+            (err_detail != NULL) ? errdetail(err_detail) : 0,
+            (err_hint != NULL) ? errhint(err_hint) : 0));
 
    estate->err_text = NULL;    /* un-suppress... */
 
-   plpgsql_dstring_free(&ds);
+   if (condname != NULL)
+       pfree(condname);
+   if (err_message != NULL)
+       pfree(err_message);
+   if (err_detail != NULL)
+       pfree(err_detail);
+   if (err_hint != NULL)
+       pfree(err_hint);
 
    return PLPGSQL_RC_OK;
 }
index f7624a6c33d6cac1f548482d072671c9eb1afa12..cb9e9c99ec3e33e1aea7f048997557fc80c7126c 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.70 2008/05/03 00:11:36 tgl Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.71 2008/05/13 22:10:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1003,7 +1003,12 @@ dump_raise(PLpgSQL_stmt_raise *stmt)
    int         i = 0;
 
    dump_ind();
-   printf("RAISE '%s'\n", stmt->message);
+   printf("RAISE level=%d", stmt->elog_level);
+   if (stmt->condname)
+       printf(" condname='%s'", stmt->condname);
+   if (stmt->message)
+       printf(" message='%s'", stmt->message);
+   printf("\n");
    dump_indent += 2;
    foreach(lc, stmt->params)
    {
@@ -1012,6 +1017,36 @@ dump_raise(PLpgSQL_stmt_raise *stmt)
        dump_expr((PLpgSQL_expr *) lfirst(lc));
        printf("\n");
    }
+   if (stmt->options)
+   {
+       dump_ind();
+       printf("    USING\n");
+       dump_indent += 2;
+       foreach(lc, stmt->options)
+       {
+           PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(lc);
+       
+           dump_ind();
+           switch (opt->opt_type)
+           {
+               case PLPGSQL_RAISEOPTION_ERRCODE:
+                   printf("    ERRCODE = ");
+                   break;
+               case PLPGSQL_RAISEOPTION_MESSAGE:
+                   printf("    MESSAGE = ");
+                   break;  
+               case PLPGSQL_RAISEOPTION_DETAIL:
+                   printf("    DETAIL = ");
+                   break;
+               case PLPGSQL_RAISEOPTION_HINT:
+                   printf("    HINT = ");
+                   break;
+           }
+           dump_expr(opt->expr);
+           printf("\n");
+       }       
+       dump_indent -= 2;
+   }
    dump_indent -= 2;
 }
 
index f1206932bb5e871af6366d2b25d87831334c28a3..53d691a596ab091226e9261f916bb721929f2582 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.98 2008/05/03 00:11:36 tgl Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.99 2008/05/13 22:10:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -106,7 +106,8 @@ enum
    PLPGSQL_RC_OK,
    PLPGSQL_RC_EXIT,
    PLPGSQL_RC_RETURN,
-   PLPGSQL_RC_CONTINUE
+   PLPGSQL_RC_CONTINUE,
+   PLPGSQL_RC_RERAISE
 };
 
 /* ----------
@@ -119,6 +120,18 @@ enum
    PLPGSQL_GETDIAG_RESULT_OID
 };
 
+/* --------
+ * RAISE statement options
+ * --------
+ */
+enum
+{
+   PLPGSQL_RAISEOPTION_ERRCODE,
+   PLPGSQL_RAISEOPTION_MESSAGE,
+   PLPGSQL_RAISEOPTION_DETAIL,
+   PLPGSQL_RAISEOPTION_HINT
+};
+
 
 /**********************************************************************
  * Node and structure definitions
@@ -539,10 +552,18 @@ typedef struct
    int         cmd_type;
    int         lineno;
    int         elog_level;
-   char       *message;
-   List       *params;         /* list of expressions */
+   char       *condname;       /* condition name, SQLSTATE, or NULL */
+   char       *message;        /* old-style message format literal, or NULL */
+   List       *params;         /* list of expressions for old-style message */
+   List       *options;        /* list of PLpgSQL_raise_option */
 } PLpgSQL_stmt_raise;
 
+typedef struct
+{                              /* RAISE statement option */
+   int         opt_type;
+   PLpgSQL_expr *expr;
+} PLpgSQL_raise_option;
+
 
 typedef struct
 {                              /* Generic SQL statement to execute */
@@ -772,6 +793,8 @@ extern PLpgSQL_variable *plpgsql_build_variable(const char *refname, int lineno,
                       bool add2namespace);
 extern PLpgSQL_rec *plpgsql_build_record(const char *refname, int lineno,
                                         bool add2namespace);
+extern int plpgsql_recognize_err_condition(const char *condname,
+                                           bool allow_sqlstate);
 extern PLpgSQL_condition *plpgsql_parse_err_condition(char *condname);
 extern void plpgsql_adddatum(PLpgSQL_datum *new);
 extern int plpgsql_add_initdatums(int **varnos);
index 38bc8a9f98dcc26f6bef521e0cba814460153274..73258ec364802ac675f8fde39b708a17665d0bb3 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.62 2008/05/09 15:36:31 petere Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.63 2008/05/13 22:10:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -120,7 +120,6 @@ close           { return K_CLOSE;           }
 constant       { return K_CONSTANT;        }
 continue       { return K_CONTINUE;        }
 cursor         { return K_CURSOR;          }
-debug          { return K_DEBUG;           }
 declare            { return K_DECLARE;         }
 default            { return K_DEFAULT;         }
 diagnostics        { return K_DIAGNOSTICS;     }
@@ -137,16 +136,13 @@ from          { return K_FROM;            }
 get                { return K_GET;             }
 if             { return K_IF;              }
 in             { return K_IN;              }
-info           { return K_INFO;            }
 insert         { return K_INSERT;          }
 into           { return K_INTO;            }
 is             { return K_IS;              }
-log                { return K_LOG;             }
 loop           { return K_LOOP;            }
 move           { return K_MOVE;            }
 no{space}+scroll { return K_NOSCROLL;      }
 not                { return K_NOT;             }
-notice         { return K_NOTICE;          }
 null           { return K_NULL;            }
 open           { return K_OPEN;            }
 or             { return K_OR;              }
@@ -163,7 +159,6 @@ then            { return K_THEN;            }
 to             { return K_TO;              }
 type           { return K_TYPE;            }
 using          { return K_USING;           }
-warning            { return K_WARNING;         }
 when           { return K_WHEN;            }
 while          { return K_WHILE;           }
 
index da987b22e460218f406c4ee5982d30f3449b018a..9f18f45126acca0b96b8fec3a536873836969cba 100644 (file)
@@ -3267,7 +3267,7 @@ end;
 $$ language plpgsql;
 ERROR:  cursor FOR loop must use a bound cursor variable
 CONTEXT:  compile of PL/pgSQL function "forc_bad" near line 4
--- return query execute
+-- test RETURN QUERY EXECUTE
 create or replace function return_dquery()
 returns setof int as $$
 begin
@@ -3285,3 +3285,132 @@ select * from return_dquery();
 (4 rows)
 
 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 
+     using errcode = '55001', detail = 'some detail info', hint = 'some hint';
+  raise '% % %', 1, 2, 3 
+     using errcode = 'division_by_zero', detail = 'some detail info';
+end;
+$$ language plpgsql;
+select raise_test();
+NOTICE:  1 2 3
+DETAIL:  some detail info
+HINT:  some hint
+ERROR:  1 2 3
+DETAIL:  some detail info
+-- Since we can't actually see the thrown SQLSTATE in default psql output,
+-- test it like this; this also tests re-RAISE
+create or replace function raise_test() returns void as $$
+begin
+  raise 'check me'
+     using errcode = 'division_by_zero', detail = 'some detail info';
+  exception
+    when others then
+      raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
+      raise;
+end;
+$$ language plpgsql;
+select raise_test();
+NOTICE:  SQLSTATE: 22012 SQLERRM: check me
+ERROR:  check me
+DETAIL:  some detail info
+create or replace function raise_test() returns void as $$
+begin
+  raise 'check me'
+     using errcode = '1234F', detail = 'some detail info';
+  exception
+    when others then
+      raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
+      raise;
+end;
+$$ language plpgsql;
+select raise_test();
+NOTICE:  SQLSTATE: 1234F SQLERRM: check me
+ERROR:  check me
+DETAIL:  some detail info
+-- SQLSTATE specification in WHEN
+create or replace function raise_test() returns void as $$
+begin
+  raise 'check me'
+     using errcode = '1234F', detail = 'some detail info';
+  exception
+    when sqlstate '1234F' then
+      raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
+      raise;
+end;
+$$ language plpgsql;
+select raise_test();
+NOTICE:  SQLSTATE: 1234F SQLERRM: check me
+ERROR:  check me
+DETAIL:  some detail info
+create or replace function raise_test() returns void as $$
+begin
+  raise division_by_zero using detail = 'some detail info';
+  exception
+    when others then
+      raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
+      raise;
+end;
+$$ language plpgsql;
+select raise_test();
+NOTICE:  SQLSTATE: 22012 SQLERRM: division_by_zero
+ERROR:  division_by_zero
+DETAIL:  some detail info
+create or replace function raise_test() returns void as $$
+begin
+  raise division_by_zero;
+end;
+$$ language plpgsql;
+select raise_test();
+ERROR:  division_by_zero
+create or replace function raise_test() returns void as $$
+begin
+  raise sqlstate '1234F';
+end;
+$$ language plpgsql;
+select raise_test();
+ERROR:  1234F
+create or replace function raise_test() returns void as $$
+begin
+  raise division_by_zero using message = 'custom' || ' message';
+end;
+$$ language plpgsql;
+select raise_test();
+ERROR:  custom message
+create or replace function raise_test() returns void as $$
+begin
+  raise using message = 'custom' || ' message', errcode = '22012';
+end;
+$$ language plpgsql;
+select raise_test();
+ERROR:  custom message
+-- conflict on message
+create or replace function raise_test() returns void as $$
+begin
+  raise notice 'some message' using message = 'custom' || ' message', errcode = '22012';
+end;
+$$ language plpgsql;
+select raise_test();
+ERROR:  RAISE option already specified: MESSAGE
+CONTEXT:  PL/pgSQL function "raise_test" line 2 at RAISE
+-- conflict on errcode
+create or replace function raise_test() returns void as $$
+begin
+  raise division_by_zero using message = 'custom' || ' message', errcode = '22012';
+end;
+$$ language plpgsql;
+select raise_test();
+ERROR:  RAISE option already specified: ERRCODE
+CONTEXT:  PL/pgSQL function "raise_test" line 2 at RAISE
+-- nothing to re-RAISE
+create or replace function raise_test() returns void as $$
+begin
+  raise;
+end;
+$$ language plpgsql;
+select raise_test();
+ERROR:  RAISE without parameters cannot be used outside an exception handler
+CONTEXT:  PL/pgSQL function "raise_test"
+drop function raise_test();
index b0799dcdc705d53acdd5ad99570cea7dc869a04a..f64bfabc1e5385d79f1d8c4af55e793151e06c59 100644 (file)
@@ -2670,7 +2670,7 @@ begin
 end;
 $$ language plpgsql;
 
--- return query execute
+-- test RETURN QUERY EXECUTE
 
 create or replace function return_dquery()
 returns setof int as $$
@@ -2683,3 +2683,132 @@ $$ language plpgsql;
 select * from return_dquery();
 
 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 
+     using errcode = '55001', detail = 'some detail info', hint = 'some hint';
+  raise '% % %', 1, 2, 3 
+     using errcode = 'division_by_zero', detail = 'some detail info';
+end;
+$$ language plpgsql;
+
+select raise_test();
+
+-- Since we can't actually see the thrown SQLSTATE in default psql output,
+-- test it like this; this also tests re-RAISE
+
+create or replace function raise_test() returns void as $$
+begin
+  raise 'check me'
+     using errcode = 'division_by_zero', detail = 'some detail info';
+  exception
+    when others then
+      raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
+      raise;
+end;
+$$ language plpgsql;
+
+select raise_test();
+
+create or replace function raise_test() returns void as $$
+begin
+  raise 'check me'
+     using errcode = '1234F', detail = 'some detail info';
+  exception
+    when others then
+      raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
+      raise;
+end;
+$$ language plpgsql;
+
+select raise_test();
+
+-- SQLSTATE specification in WHEN
+create or replace function raise_test() returns void as $$
+begin
+  raise 'check me'
+     using errcode = '1234F', detail = 'some detail info';
+  exception
+    when sqlstate '1234F' then
+      raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
+      raise;
+end;
+$$ language plpgsql;
+
+select raise_test();
+
+create or replace function raise_test() returns void as $$
+begin
+  raise division_by_zero using detail = 'some detail info';
+  exception
+    when others then
+      raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
+      raise;
+end;
+$$ language plpgsql;
+
+select raise_test();
+
+create or replace function raise_test() returns void as $$
+begin
+  raise division_by_zero;
+end;
+$$ language plpgsql;
+
+select raise_test();
+
+create or replace function raise_test() returns void as $$
+begin
+  raise sqlstate '1234F';
+end;
+$$ language plpgsql;
+
+select raise_test();
+
+create or replace function raise_test() returns void as $$
+begin
+  raise division_by_zero using message = 'custom' || ' message';
+end;
+$$ language plpgsql;
+
+select raise_test();
+
+create or replace function raise_test() returns void as $$
+begin
+  raise using message = 'custom' || ' message', errcode = '22012';
+end;
+$$ language plpgsql;
+
+select raise_test();
+
+-- conflict on message
+create or replace function raise_test() returns void as $$
+begin
+  raise notice 'some message' using message = 'custom' || ' message', errcode = '22012';
+end;
+$$ language plpgsql;
+
+select raise_test();
+
+-- conflict on errcode
+create or replace function raise_test() returns void as $$
+begin
+  raise division_by_zero using message = 'custom' || ' message', errcode = '22012';
+end;
+$$ language plpgsql;
+
+select raise_test();
+
+-- nothing to re-RAISE
+create or replace function raise_test() returns void as $$
+begin
+  raise;
+end;
+$$ language plpgsql;
+
+select raise_test();
+
+drop function raise_test();