mirror of
https://github.com/postgres/postgres.git
synced 2025-07-28 23:42:10 +03:00
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:
@ -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">
|
<chapter id="plpgsql">
|
||||||
<title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title>
|
<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
|
condition name <literal>OTHERS</> matches every error type except
|
||||||
<literal>QUERY_CANCELED</>. (It is possible, but often unwise,
|
<literal>QUERY_CANCELED</>. (It is possible, but often unwise,
|
||||||
to trap <literal>QUERY_CANCELED</> by name.) Condition names are
|
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>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
@ -2750,13 +2755,19 @@ END LOOP <optional> <replaceable>label</replaceable> </optional>;
|
|||||||
raise errors.
|
raise errors.
|
||||||
|
|
||||||
<synopsis>
|
<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>
|
</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>LOG</literal>, <literal>INFO</literal>,
|
||||||
<literal>NOTICE</literal>, <literal>WARNING</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
|
<literal>EXCEPTION</literal> raises an error (which normally aborts the
|
||||||
current transaction); the other levels only generate messages of different
|
current transaction); the other levels only generate messages of different
|
||||||
priority levels.
|
priority levels.
|
||||||
@ -2769,19 +2780,17 @@ RAISE <replaceable class="parameter">level</replaceable> '<replaceable class="pa
|
|||||||
</para>
|
</para>
|
||||||
|
|
||||||
<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
|
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>.
|
<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>
|
</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>
|
<para>
|
||||||
In this example, the value of <literal>v_job_id</> will replace the
|
In this example, the value of <literal>v_job_id</> will replace the
|
||||||
<literal>%</literal> in the string:
|
<literal>%</literal> in the string:
|
||||||
@ -2791,19 +2800,90 @@ RAISE NOTICE 'Calling cs_create_job(%)', v_job_id;
|
|||||||
</para>
|
</para>
|
||||||
|
|
||||||
<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>
|
<programlisting>
|
||||||
RAISE EXCEPTION 'Nonexistent ID --> %', user_id;
|
RAISE EXCEPTION 'Nonexistent ID --> %', user_id USING HINT = 'Please check your user id';
|
||||||
</programlisting>
|
</programlisting>
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
<command>RAISE EXCEPTION</command> presently always generates
|
These two examples show equivalent ways of setting the SQLSTATE:
|
||||||
the same <varname>SQLSTATE</varname> code, <literal>P0001</>, no matter what message
|
<programlisting>
|
||||||
it is invoked with. It is possible to trap this exception with
|
RAISE 'Duplicate user ID: %', user_id USING ERRCODE = 'unique_violation';
|
||||||
<literal>EXCEPTION ... WHEN RAISE_EXCEPTION THEN ...</> but there
|
RAISE 'Duplicate user ID: %', user_id USING ERRCODE = '23505';
|
||||||
is no way to tell one <command>RAISE</> from another.
|
</programlisting>
|
||||||
</para>
|
</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>
|
||||||
|
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>
|
||||||
|
|
||||||
<sect1 id="plpgsql-trigger">
|
<sect1 id="plpgsql-trigger">
|
||||||
@ -4307,7 +4387,9 @@ $$ LANGUAGE plpgsql;
|
|||||||
<callout arearefs="co.plpgsql-porting-raise">
|
<callout arearefs="co.plpgsql-porting-raise">
|
||||||
<para>
|
<para>
|
||||||
The syntax of <literal>RAISE</> is considerably different from
|
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>
|
</para>
|
||||||
</callout>
|
</callout>
|
||||||
<callout arearefs="co.plpgsql-porting-exception">
|
<callout arearefs="co.plpgsql-porting-exception">
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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);
|
const char *end_label);
|
||||||
static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor,
|
static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor,
|
||||||
int until, const char *expected);
|
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 <list> proc_exceptions
|
||||||
%type <exception_block> exception_sect
|
%type <exception_block> exception_sect
|
||||||
%type <exception> proc_exception
|
%type <exception> proc_exception
|
||||||
%type <condition> proc_conditions
|
%type <condition> proc_conditions proc_condition
|
||||||
|
|
||||||
|
|
||||||
%type <ival> raise_level
|
|
||||||
%type <str> raise_msg
|
|
||||||
|
|
||||||
%type <list> getdiag_list
|
%type <list> getdiag_list
|
||||||
%type <diagitem> getdiag_list_item
|
%type <diagitem> getdiag_list_item
|
||||||
@ -164,7 +161,6 @@ static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor,
|
|||||||
%token K_CONSTANT
|
%token K_CONSTANT
|
||||||
%token K_CONTINUE
|
%token K_CONTINUE
|
||||||
%token K_CURSOR
|
%token K_CURSOR
|
||||||
%token K_DEBUG
|
|
||||||
%token K_DECLARE
|
%token K_DECLARE
|
||||||
%token K_DEFAULT
|
%token K_DEFAULT
|
||||||
%token K_DIAGNOSTICS
|
%token K_DIAGNOSTICS
|
||||||
@ -181,16 +177,13 @@ static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor,
|
|||||||
%token K_GET
|
%token K_GET
|
||||||
%token K_IF
|
%token K_IF
|
||||||
%token K_IN
|
%token K_IN
|
||||||
%token K_INFO
|
|
||||||
%token K_INSERT
|
%token K_INSERT
|
||||||
%token K_INTO
|
%token K_INTO
|
||||||
%token K_IS
|
%token K_IS
|
||||||
%token K_LOG
|
|
||||||
%token K_LOOP
|
%token K_LOOP
|
||||||
%token K_MOVE
|
%token K_MOVE
|
||||||
%token K_NOSCROLL
|
%token K_NOSCROLL
|
||||||
%token K_NOT
|
%token K_NOT
|
||||||
%token K_NOTICE
|
|
||||||
%token K_NULL
|
%token K_NULL
|
||||||
%token K_OPEN
|
%token K_OPEN
|
||||||
%token K_OR
|
%token K_OR
|
||||||
@ -207,7 +200,6 @@ static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor,
|
|||||||
%token K_TO
|
%token K_TO
|
||||||
%token K_TYPE
|
%token K_TYPE
|
||||||
%token K_USING
|
%token K_USING
|
||||||
%token K_WARNING
|
|
||||||
%token K_WHEN
|
%token K_WHEN
|
||||||
%token K_WHILE
|
%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;
|
PLpgSQL_stmt_raise *new;
|
||||||
int tok;
|
int tok;
|
||||||
@ -1255,69 +1247,133 @@ stmt_raise : K_RAISE lno raise_level raise_msg
|
|||||||
|
|
||||||
new->cmd_type = PLPGSQL_STMT_RAISE;
|
new->cmd_type = PLPGSQL_STMT_RAISE;
|
||||||
new->lineno = $2;
|
new->lineno = $2;
|
||||||
new->elog_level = $3;
|
new->elog_level = ERROR; /* default */
|
||||||
new->message = $4;
|
new->condname = NULL;
|
||||||
|
new->message = NULL;
|
||||||
new->params = NIL;
|
new->params = NIL;
|
||||||
|
new->options = NIL;
|
||||||
|
|
||||||
tok = yylex();
|
tok = yylex();
|
||||||
|
if (tok == 0)
|
||||||
|
yyerror("unexpected end of function definition");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We could have just RAISE, meaning to re-throw
|
||||||
|
* the current error.
|
||||||
|
*/
|
||||||
|
if (tok != ';')
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* 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)
|
||||||
|
{
|
||||||
|
new->elog_level = DEBUG1;
|
||||||
|
tok = yylex();
|
||||||
|
}
|
||||||
|
if (tok == 0)
|
||||||
|
yyerror("unexpected end of function definition");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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
|
* We expect either a semi-colon, which
|
||||||
* indicates no parameters, or a comma that
|
* indicates no parameters, or a comma that
|
||||||
* begins the list of parameter expressions
|
* begins the list of parameter expressions,
|
||||||
|
* or USING to begin the options list.
|
||||||
*/
|
*/
|
||||||
if (tok != ',' && tok != ';')
|
tok = yylex();
|
||||||
|
if (tok != ',' && tok != ';' && tok != K_USING)
|
||||||
yyerror("syntax error");
|
yyerror("syntax error");
|
||||||
|
|
||||||
if (tok == ',')
|
while (tok == ',')
|
||||||
{
|
|
||||||
do
|
|
||||||
{
|
{
|
||||||
PLpgSQL_expr *expr;
|
PLpgSQL_expr *expr;
|
||||||
|
|
||||||
expr = read_sql_expression2(',', ';',
|
expr = read_sql_construct(',', ';', K_USING,
|
||||||
", or ;",
|
", or ; or USING",
|
||||||
&tok);
|
"SELECT ",
|
||||||
|
true, true, &tok);
|
||||||
new->params = lappend(new->params, expr);
|
new->params = lappend(new->params, expr);
|
||||||
} while (tok == ',');
|
}
|
||||||
|
}
|
||||||
|
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;
|
$$ = (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 ';'
|
loop_body : proc_sect K_END K_LOOP opt_label ';'
|
||||||
{
|
{
|
||||||
$$.stmts = $1;
|
$$.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;
|
PLpgSQL_condition *old;
|
||||||
|
|
||||||
for (old = $1; old->next != NULL; old = old->next)
|
for (old = $1; old->next != NULL; old = old->next)
|
||||||
/* skip */ ;
|
/* skip */ ;
|
||||||
old->next = plpgsql_parse_err_condition($3);
|
old->next = $3;
|
||||||
|
|
||||||
$$ = $1;
|
$$ = $1;
|
||||||
}
|
}
|
||||||
| opt_lblname
|
| proc_condition
|
||||||
|
{
|
||||||
|
$$ = $1;
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
proc_condition : opt_lblname
|
||||||
{
|
{
|
||||||
$$ = plpgsql_parse_err_condition($1);
|
$$ = 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 :
|
expr_until_semi :
|
||||||
@ -2658,6 +2755,55 @@ read_cursor_args(PLpgSQL_var *cursor, int until, const char *expected)
|
|||||||
return expr;
|
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: */
|
/* Needed to avoid conflict between different prefix settings: */
|
||||||
#undef yylex
|
#undef yylex
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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;
|
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
|
* plpgsql_parse_err_condition
|
||||||
* Generate PLpgSQL_condition entry(s) for an exception condition name
|
* Generate PLpgSQL_condition entry(s) for an exception condition name
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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;
|
estate.err_text = NULL;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Provide a more helpful message if a CONTINUE has been used outside
|
* Provide a more helpful message if a CONTINUE or RAISE has been used
|
||||||
* a loop.
|
* outside the context it can work in.
|
||||||
*/
|
*/
|
||||||
if (rc == PLPGSQL_RC_CONTINUE)
|
if (rc == PLPGSQL_RC_CONTINUE)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
errmsg("CONTINUE cannot be used outside a loop")));
|
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
|
else
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
|
(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
|
||||||
@ -662,13 +666,17 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
|
|||||||
estate.err_text = NULL;
|
estate.err_text = NULL;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Provide a more helpful message if a CONTINUE has been used outside
|
* Provide a more helpful message if a CONTINUE or RAISE has been used
|
||||||
* a loop.
|
* outside the context it can work in.
|
||||||
*/
|
*/
|
||||||
if (rc == PLPGSQL_RC_CONTINUE)
|
if (rc == PLPGSQL_RC_CONTINUE)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
errmsg("CONTINUE cannot be used outside a loop")));
|
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
|
else
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
|
(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);
|
free_var(errm_var);
|
||||||
errm_var->value = (Datum) 0;
|
errm_var->value = (Datum) 0;
|
||||||
errm_var->isnull = true;
|
errm_var->isnull = true;
|
||||||
|
|
||||||
|
/* re-throw error if requested by handler */
|
||||||
|
if (rc == PLPGSQL_RC_RERAISE)
|
||||||
|
ReThrowError(edata);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1139,8 +1152,9 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
|
|||||||
switch (rc)
|
switch (rc)
|
||||||
{
|
{
|
||||||
case PLPGSQL_RC_OK:
|
case PLPGSQL_RC_OK:
|
||||||
case PLPGSQL_RC_CONTINUE:
|
|
||||||
case PLPGSQL_RC_RETURN:
|
case PLPGSQL_RC_RETURN:
|
||||||
|
case PLPGSQL_RC_CONTINUE:
|
||||||
|
case PLPGSQL_RC_RERAISE:
|
||||||
return rc;
|
return rc;
|
||||||
|
|
||||||
case PLPGSQL_RC_EXIT:
|
case PLPGSQL_RC_EXIT:
|
||||||
@ -1469,7 +1483,8 @@ exec_stmt_loop(PLpgSQL_execstate *estate, PLpgSQL_stmt_loop *stmt)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case PLPGSQL_RC_RETURN:
|
case PLPGSQL_RC_RETURN:
|
||||||
return PLPGSQL_RC_RETURN;
|
case PLPGSQL_RC_RERAISE:
|
||||||
|
return rc;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
elog(ERROR, "unrecognized rc: %d", rc);
|
elog(ERROR, "unrecognized rc: %d", rc);
|
||||||
@ -1532,7 +1547,8 @@ exec_stmt_while(PLpgSQL_execstate *estate, PLpgSQL_stmt_while *stmt)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case PLPGSQL_RC_RETURN:
|
case PLPGSQL_RC_RETURN:
|
||||||
return PLPGSQL_RC_RETURN;
|
case PLPGSQL_RC_RERAISE:
|
||||||
|
return rc;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
elog(ERROR, "unrecognized rc: %d", rc);
|
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);
|
rc = exec_stmts(estate, stmt->body);
|
||||||
|
|
||||||
if (rc == PLPGSQL_RC_RETURN)
|
if (rc == PLPGSQL_RC_RETURN ||
|
||||||
break; /* return from function */
|
rc == PLPGSQL_RC_RERAISE)
|
||||||
|
break; /* break out of the loop */
|
||||||
else if (rc == PLPGSQL_RC_EXIT)
|
else if (rc == PLPGSQL_RC_EXIT)
|
||||||
{
|
{
|
||||||
if (estate->exitlabel == NULL)
|
if (estate->exitlabel == NULL)
|
||||||
@ -2267,9 +2284,29 @@ exec_init_tuple_store(PLpgSQL_execstate *estate)
|
|||||||
static int
|
static int
|
||||||
exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
|
exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
|
||||||
{
|
{
|
||||||
char *cp;
|
int err_code = 0;
|
||||||
|
char *condname = NULL;
|
||||||
|
char *err_message = NULL;
|
||||||
|
char *err_detail = NULL;
|
||||||
|
char *err_hint = NULL;
|
||||||
|
ListCell *lc;
|
||||||
|
|
||||||
|
/* RAISE with no parameters: re-throw current exception */
|
||||||
|
if (stmt->condname == NULL && stmt->message == NULL &&
|
||||||
|
stmt->options == NIL)
|
||||||
|
return PLPGSQL_RC_RERAISE;
|
||||||
|
|
||||||
|
if (stmt->condname)
|
||||||
|
{
|
||||||
|
err_code = plpgsql_recognize_err_condition(stmt->condname, true);
|
||||||
|
condname = pstrdup(stmt->condname);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stmt->message)
|
||||||
|
{
|
||||||
PLpgSQL_dstring ds;
|
PLpgSQL_dstring ds;
|
||||||
ListCell *current_param;
|
ListCell *current_param;
|
||||||
|
char *cp;
|
||||||
|
|
||||||
plpgsql_dstring_init(&ds);
|
plpgsql_dstring_init(&ds);
|
||||||
current_param = list_head(stmt->params);
|
current_param = list_head(stmt->params);
|
||||||
@ -2311,9 +2348,8 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
|
|||||||
plpgsql_dstring_append(&ds, extval);
|
plpgsql_dstring_append(&ds, extval);
|
||||||
current_param = lnext(current_param);
|
current_param = lnext(current_param);
|
||||||
exec_eval_cleanup(estate);
|
exec_eval_cleanup(estate);
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
plpgsql_dstring_append_char(&ds, cp[0]);
|
plpgsql_dstring_append_char(&ds, cp[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2326,18 +2362,107 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
|
|||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
errmsg("too many parameters specified for RAISE")));
|
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)
|
* Throw the error (may or may not come back)
|
||||||
*/
|
*/
|
||||||
estate->err_text = raise_skip_msg; /* suppress traceback of raise */
|
estate->err_text = raise_skip_msg; /* suppress traceback of raise */
|
||||||
|
|
||||||
ereport(stmt->elog_level,
|
ereport(stmt->elog_level,
|
||||||
((stmt->elog_level >= ERROR) ? errcode(ERRCODE_RAISE_EXCEPTION) : 0,
|
(err_code ? errcode(err_code) : 0,
|
||||||
errmsg_internal("%s", plpgsql_dstring_get(&ds))));
|
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... */
|
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;
|
return PLPGSQL_RC_OK;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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;
|
int i = 0;
|
||||||
|
|
||||||
dump_ind();
|
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;
|
dump_indent += 2;
|
||||||
foreach(lc, stmt->params)
|
foreach(lc, stmt->params)
|
||||||
{
|
{
|
||||||
@ -1012,6 +1017,36 @@ dump_raise(PLpgSQL_stmt_raise *stmt)
|
|||||||
dump_expr((PLpgSQL_expr *) lfirst(lc));
|
dump_expr((PLpgSQL_expr *) lfirst(lc));
|
||||||
printf("\n");
|
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;
|
dump_indent -= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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_OK,
|
||||||
PLPGSQL_RC_EXIT,
|
PLPGSQL_RC_EXIT,
|
||||||
PLPGSQL_RC_RETURN,
|
PLPGSQL_RC_RETURN,
|
||||||
PLPGSQL_RC_CONTINUE
|
PLPGSQL_RC_CONTINUE,
|
||||||
|
PLPGSQL_RC_RERAISE
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ----------
|
/* ----------
|
||||||
@ -119,6 +120,18 @@ enum
|
|||||||
PLPGSQL_GETDIAG_RESULT_OID
|
PLPGSQL_GETDIAG_RESULT_OID
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* --------
|
||||||
|
* RAISE statement options
|
||||||
|
* --------
|
||||||
|
*/
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
PLPGSQL_RAISEOPTION_ERRCODE,
|
||||||
|
PLPGSQL_RAISEOPTION_MESSAGE,
|
||||||
|
PLPGSQL_RAISEOPTION_DETAIL,
|
||||||
|
PLPGSQL_RAISEOPTION_HINT
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* Node and structure definitions
|
* Node and structure definitions
|
||||||
@ -539,10 +552,18 @@ typedef struct
|
|||||||
int cmd_type;
|
int cmd_type;
|
||||||
int lineno;
|
int lineno;
|
||||||
int elog_level;
|
int elog_level;
|
||||||
char *message;
|
char *condname; /* condition name, SQLSTATE, or NULL */
|
||||||
List *params; /* list of expressions */
|
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;
|
} PLpgSQL_stmt_raise;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{ /* RAISE statement option */
|
||||||
|
int opt_type;
|
||||||
|
PLpgSQL_expr *expr;
|
||||||
|
} PLpgSQL_raise_option;
|
||||||
|
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{ /* Generic SQL statement to execute */
|
{ /* Generic SQL statement to execute */
|
||||||
@ -772,6 +793,8 @@ extern PLpgSQL_variable *plpgsql_build_variable(const char *refname, int lineno,
|
|||||||
bool add2namespace);
|
bool add2namespace);
|
||||||
extern PLpgSQL_rec *plpgsql_build_record(const char *refname, int lineno,
|
extern PLpgSQL_rec *plpgsql_build_record(const char *refname, int lineno,
|
||||||
bool add2namespace);
|
bool add2namespace);
|
||||||
|
extern int plpgsql_recognize_err_condition(const char *condname,
|
||||||
|
bool allow_sqlstate);
|
||||||
extern PLpgSQL_condition *plpgsql_parse_err_condition(char *condname);
|
extern PLpgSQL_condition *plpgsql_parse_err_condition(char *condname);
|
||||||
extern void plpgsql_adddatum(PLpgSQL_datum *new);
|
extern void plpgsql_adddatum(PLpgSQL_datum *new);
|
||||||
extern int plpgsql_add_initdatums(int **varnos);
|
extern int plpgsql_add_initdatums(int **varnos);
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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; }
|
constant { return K_CONSTANT; }
|
||||||
continue { return K_CONTINUE; }
|
continue { return K_CONTINUE; }
|
||||||
cursor { return K_CURSOR; }
|
cursor { return K_CURSOR; }
|
||||||
debug { return K_DEBUG; }
|
|
||||||
declare { return K_DECLARE; }
|
declare { return K_DECLARE; }
|
||||||
default { return K_DEFAULT; }
|
default { return K_DEFAULT; }
|
||||||
diagnostics { return K_DIAGNOSTICS; }
|
diagnostics { return K_DIAGNOSTICS; }
|
||||||
@ -137,16 +136,13 @@ from { return K_FROM; }
|
|||||||
get { return K_GET; }
|
get { return K_GET; }
|
||||||
if { return K_IF; }
|
if { return K_IF; }
|
||||||
in { return K_IN; }
|
in { return K_IN; }
|
||||||
info { return K_INFO; }
|
|
||||||
insert { return K_INSERT; }
|
insert { return K_INSERT; }
|
||||||
into { return K_INTO; }
|
into { return K_INTO; }
|
||||||
is { return K_IS; }
|
is { return K_IS; }
|
||||||
log { return K_LOG; }
|
|
||||||
loop { return K_LOOP; }
|
loop { return K_LOOP; }
|
||||||
move { return K_MOVE; }
|
move { return K_MOVE; }
|
||||||
no{space}+scroll { return K_NOSCROLL; }
|
no{space}+scroll { return K_NOSCROLL; }
|
||||||
not { return K_NOT; }
|
not { return K_NOT; }
|
||||||
notice { return K_NOTICE; }
|
|
||||||
null { return K_NULL; }
|
null { return K_NULL; }
|
||||||
open { return K_OPEN; }
|
open { return K_OPEN; }
|
||||||
or { return K_OR; }
|
or { return K_OR; }
|
||||||
@ -163,7 +159,6 @@ then { return K_THEN; }
|
|||||||
to { return K_TO; }
|
to { return K_TO; }
|
||||||
type { return K_TYPE; }
|
type { return K_TYPE; }
|
||||||
using { return K_USING; }
|
using { return K_USING; }
|
||||||
warning { return K_WARNING; }
|
|
||||||
when { return K_WHEN; }
|
when { return K_WHEN; }
|
||||||
while { return K_WHILE; }
|
while { return K_WHILE; }
|
||||||
|
|
||||||
|
@ -3267,7 +3267,7 @@ end;
|
|||||||
$$ language plpgsql;
|
$$ language plpgsql;
|
||||||
ERROR: cursor FOR loop must use a bound cursor variable
|
ERROR: cursor FOR loop must use a bound cursor variable
|
||||||
CONTEXT: compile of PL/pgSQL function "forc_bad" near line 4
|
CONTEXT: compile of PL/pgSQL function "forc_bad" near line 4
|
||||||
-- return query execute
|
-- test RETURN QUERY EXECUTE
|
||||||
create or replace function return_dquery()
|
create or replace function return_dquery()
|
||||||
returns setof int as $$
|
returns setof int as $$
|
||||||
begin
|
begin
|
||||||
@ -3285,3 +3285,132 @@ select * from return_dquery();
|
|||||||
(4 rows)
|
(4 rows)
|
||||||
|
|
||||||
drop function 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();
|
||||||
|
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();
|
||||||
|
@ -2670,7 +2670,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
$$ language plpgsql;
|
$$ language plpgsql;
|
||||||
|
|
||||||
-- return query execute
|
-- test RETURN QUERY EXECUTE
|
||||||
|
|
||||||
create or replace function return_dquery()
|
create or replace function return_dquery()
|
||||||
returns setof int as $$
|
returns setof int as $$
|
||||||
@ -2683,3 +2683,132 @@ $$ language plpgsql;
|
|||||||
select * from return_dquery();
|
select * from return_dquery();
|
||||||
|
|
||||||
drop function 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();
|
||||||
|
Reference in New Issue
Block a user