Improve plpgsql's RAISE command. It is now possible to attach DETAIL and
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 13 May 2008 22:10:30 +0000 (22:10 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
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 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.128 2008/05/03 00:11:36 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.129 2008/05/13 22:10:29 tgl Exp $ -->
 
 <chapter id="plpgsql">
   <title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title>
@@ -2133,7 +2133,12 @@ END;
      condition name <literal>OTHERS</> matches every error type except
      <literal>QUERY_CANCELED</>.  (It is possible, but often unwise,
      to trap <literal>QUERY_CANCELED</> by name.)  Condition names are
-     not case-sensitive.
+     not case-sensitive.  Also, an error condition can be specified
+     by <literal>SQLSTATE</> code; for example these are equivalent:
+<programlisting>
+        WHEN division_by_zero THEN ...
+        WHEN SQLSTATE '22012' THEN ...
+</programlisting>
     </para>
 
     <para>
@@ -2750,13 +2755,19 @@ END LOOP <optional> <replaceable>label</replaceable> </optional>;
     raise errors.
 
 <synopsis>
-RAISE <replaceable class="parameter">level</replaceable> '<replaceable class="parameter">format</replaceable>' <optional>, <replaceable class="parameter">expression</replaceable> <optional>, ...</optional></optional>;
+RAISE <optional> <replaceable class="parameter">level</replaceable> </optional> '<replaceable class="parameter">format</replaceable>' <optional>, <replaceable class="parameter">expression</replaceable> <optional>, ...</optional></optional> <optional> USING <replaceable class="parameter">option</replaceable> = <replaceable class="parameter">expression</replaceable> <optional>, ... </optional> </optional>;
+RAISE <optional> <replaceable class="parameter">level</replaceable> </optional> <replaceable class="parameter">condition_name</> <optional> USING <replaceable class="parameter">option</replaceable> = <replaceable class="parameter">expression</replaceable> <optional>, ... </optional> </optional>;
+RAISE <optional> <replaceable class="parameter">level</replaceable> </optional> SQLSTATE '<replaceable class="parameter">sqlstate</>' <optional> USING <replaceable class="parameter">option</replaceable> = <replaceable class="parameter">expression</replaceable> <optional>, ... </optional> </optional>;
+RAISE <optional> <replaceable class="parameter">level</replaceable> </optional> USING <replaceable class="parameter">option</replaceable> = <replaceable class="parameter">expression</replaceable> <optional>, ... </optional>;
+RAISE ;
 </synopsis>
 
-    Possible levels are <literal>DEBUG</literal>,
+    The <replaceable class="parameter">level</replaceable> option specifies
+    the error severity.  Allowed levels are <literal>DEBUG</literal>,
     <literal>LOG</literal>, <literal>INFO</literal>,
     <literal>NOTICE</literal>, <literal>WARNING</literal>,
-    and <literal>EXCEPTION</literal>.
+    and <literal>EXCEPTION</literal>, with <literal>EXCEPTION</literal>
+    being the default.
     <literal>EXCEPTION</literal> raises an error (which normally aborts the
     current transaction); the other levels only generate messages of different
     priority levels.
@@ -2769,19 +2780,17 @@ RAISE <replaceable class="parameter">level</replaceable> '<replaceable class="pa
    </para>
 
    <para>
+    After <replaceable class="parameter">level</replaceable> if any,
+    you can write a <replaceable class="parameter">format</replaceable>
+    (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, <literal>%</literal> is replaced by the
-    next optional argument's string representation. Write
+    string representation of the next optional argument's value. Write
     <literal>%%</literal> to emit a literal <literal>%</literal>.
-    Arguments can be simple variables or expressions,
-    but the format must be a simple string literal.
    </para>
 
-   <!--
-   This example should work, but does not:
-        RAISE NOTICE 'Id number ' || key || ' not found!';
-   Put it back when we allow non-string-literal formats.
-    -->
-
    <para>
     In this example, the value of <literal>v_job_id</> will replace the
     <literal>%</literal> in the string:
@@ -2791,19 +2800,90 @@ RAISE NOTICE 'Calling cs_create_job(%)', v_job_id;
    </para>
 
    <para>
-    This example will abort the transaction with the given error message:
+    You can attach additional information to the error report by writing
+    <literal>USING</> followed by <replaceable
+    class="parameter">option</replaceable> = <replaceable
+    class="parameter">expression</replaceable> items.  The allowed
+    <replaceable class="parameter">option</replaceable> keywords are
+    <literal>MESSAGE</>, <literal>DETAIL</>, <literal>HINT</>, and
+    <literal>ERRCODE</>, while each <replaceable
+    class="parameter">expression</replaceable> can be any string-valued
+    expression.
+    <literal>MESSAGE</> sets the error message text (this option can't
+    be used in the form of <command>RAISE</> that includes a format
+    string before <literal>USING</>).
+    <literal>DETAIL</> supplies an error detail message, while
+    <literal>HINT</> supplies a hint message.
+    <literal>ERRCODE</> specifies the error code (SQLSTATE) to report,
+    either by condition name as shown in <xref linkend="errcodes-appendix">,
+    or directly as a five-character SQLSTATE code.
+   </para>
+
+   <para>
+    This example will abort the transaction with the given error message
+    and hint:
+<programlisting>
+RAISE EXCEPTION 'Nonexistent ID --> %', user_id USING HINT = 'Please check your user id';
+</programlisting>
+   </para>
+
+   <para>
+    These two examples show equivalent ways of setting the SQLSTATE:
+<programlisting>
+RAISE 'Duplicate user ID: %', user_id USING ERRCODE = 'unique_violation';
+RAISE 'Duplicate user ID: %', user_id USING ERRCODE = '23505';
+</programlisting>
+   </para>
+
+   <para>
+    There is a second <command>RAISE</> syntax in which the main argument
+    is the condition name or SQLSTATE to be reported, for example:
+<programlisting>
+RAISE division_by_zero;
+RAISE SQLSTATE '22012';
+</programlisting>
+    In this syntax, <literal>USING</> can be used to supply a custom
+    error message, detail, or hint.  Another way to do the earlier
+    example is
 <programlisting>
-RAISE EXCEPTION 'Nonexistent ID --> %', user_id;
+RAISE unique_violation USING MESSAGE = 'Duplicate user ID: ' || user_id;
 </programlisting>
    </para>
 
+   <para>
+    Still another variant is to write <literal>RAISE USING</> or <literal>RAISE
+    <replaceable class="parameter">level</replaceable> USING</> and put
+    everything else into the <literal>USING</> list.
+   </para>
+
+   <para>
+    The last variant of <command>RAISE</> has no parameters at all.
+    This form can only be used inside a <literal>BEGIN</> block's
+    <literal>EXCEPTION</> clause;
+    it causes the error currently being handled to be re-thrown to the
+    next enclosing block.
+   </para>
+
+   <para>
+    If no condition name nor SQLSTATE is specified in a
+    <command>RAISE EXCEPTION</command> command, the default is to use
+    <literal>RAISE_EXCEPTION</> (<literal>P0001</>).  If no message
+    text is specified, the default is to use the condition name or
+    SQLSTATE as message text.
+   </para>
+
+   <note>
     <para>
-     <command>RAISE EXCEPTION</command> presently always generates
-     the same <varname>SQLSTATE</varname> code, <literal>P0001</>, no matter what message
-     it is invoked with.  It is possible to trap this exception with
-     <literal>EXCEPTION ... WHEN RAISE_EXCEPTION THEN ...</> but there
-     is no way to tell one <command>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 <literal>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.
     </para>
+   </note>
+
  </sect1>
 
  <sect1 id="plpgsql-trigger">
@@ -4307,7 +4387,9 @@ $$ LANGUAGE plpgsql;
      <callout arearefs="co.plpgsql-porting-raise">
       <para>
        The syntax of <literal>RAISE</> is considerably different from
-       Oracle's similar statement.
+       Oracle's statement, although the basic case <literal>RAISE</>
+       <replaceable class="parameter">exception_name</replaceable> works
+       similarly.
       </para>
      </callout>
      <callout arearefs="co.plpgsql-porting-exception">
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 <list>   proc_exceptions
 %type <exception_block> exception_sect
 %type <exception>      proc_exception
-%type <condition>      proc_conditions
-
-
-%type <ival>   raise_level
-%type <str>            raise_msg
+%type <condition>      proc_conditions proc_condition
 
 %type <list>   getdiag_list
 %type <diagitem> 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),
-                                                                               &paramisnull,
-                                                                               &paramtypeid);
+                               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),
+                                                                                       &paramisnull,
+                                                                                       &paramtypeid);
 
-                       if (paramisnull)
-                               extval = "<NULL>";
+                               if (paramisnull)
+                                       extval = "<NULL>";
+                               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();