Improve plpgsql's RAISE command. It is now possible to attach DETAIL and

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
This commit is contained in:
Tom Lane 2008-05-13 22:10:30 +00:00
parent 72e2db86b9
commit 4107478d37
9 changed files with 867 additions and 167 deletions

View 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;
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 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">

View 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,69 +1247,133 @@ 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)
{
PLpgSQL_expr *expr;
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)
{
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");
while (tok == ',')
{
PLpgSQL_expr *expr;
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");
}
if (tok == K_USING)
new->options = read_raise_options();
}
$$ = (PLpgSQL_stmt *)new;
}
;
raise_msg : T_STRING
{
$$ = plpgsql_get_string_value();
}
;
raise_level : K_EXCEPTION
{
$$ = ERROR;
}
| K_WARNING
{
$$ = WARNING;
}
| K_NOTICE
{
$$ = NOTICE;
}
| K_INFO
{
$$ = INFO;
}
| K_LOG
{
$$ = LOG;
}
| K_DEBUG
{
$$ = DEBUG1;
}
;
loop_body : proc_sect K_END K_LOOP opt_label ';'
{
$$.stmts = $1;
@ -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

View 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

View 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;
if (cp[1] == '%')
{
plpgsql_dstring_append_char(&ds, cp[1]);
cp++;
continue;
}
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>";
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]);
err_code = plpgsql_recognize_err_condition(stmt->condname, true);
condname = pstrdup(stmt->condname);
}
/*
* 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")));
if (stmt->message)
{
PLpgSQL_dstring ds;
ListCell *current_param;
char *cp;
plpgsql_dstring_init(&ds);
current_param = list_head(stmt->params);
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] == '%')
{
Oid paramtypeid;
Datum paramvalue;
bool paramisnull;
char *extval;
if (cp[1] == '%')
{
plpgsql_dstring_append_char(&ds, cp[1]);
cp++;
continue;
}
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>";
else
extval = convert_value_to_string(paramvalue, paramtypeid);
plpgsql_dstring_append(&ds, extval);
current_param = lnext(current_param);
exec_eval_cleanup(estate);
}
else
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 */
}
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;
}

View 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;
}

View 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);

View 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; }

View 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();

View 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();