Commit 3d4890c0 authored by Tom Lane's avatar Tom Lane

Add GET STACKED DIAGNOSTICS plpgsql command to retrieve exception info.

This is more SQL-spec-compliant, more easily extensible, and better
performing than the old method of inventing special variables.

Pavel Stehule, reviewed by Shigeru Hanada and David Wheeler
parent 3406dd22
...@@ -1387,11 +1387,11 @@ EXECUTE format('UPDATE tbl SET %I = $1 WHERE key = $2', colname) ...@@ -1387,11 +1387,11 @@ EXECUTE format('UPDATE tbl SET %I = $1 WHERE key = $2', colname)
command, which has the form: command, which has the form:
<synopsis> <synopsis>
GET DIAGNOSTICS <replaceable>variable</replaceable> = <replaceable>item</replaceable> <optional> , ... </optional>; GET <optional> CURRENT </optional> DIAGNOSTICS <replaceable>variable</replaceable> = <replaceable>item</replaceable> <optional> , ... </optional>;
</synopsis> </synopsis>
This command allows retrieval of system status indicators. Each This command allows retrieval of system status indicators. Each
<replaceable>item</replaceable> is a key word identifying a state <replaceable>item</replaceable> is a key word identifying a status
value to be assigned to the specified variable (which should be value to be assigned to the specified variable (which should be
of the right data type to receive it). The currently available of the right data type to receive it). The currently available
status items are <varname>ROW_COUNT</>, the number of rows status items are <varname>ROW_COUNT</>, the number of rows
...@@ -2522,16 +2522,6 @@ END; ...@@ -2522,16 +2522,6 @@ END;
</para> </para>
</tip> </tip>
<para>
Within an exception handler, the <varname>SQLSTATE</varname>
variable contains the error code that corresponds to the
exception that was raised (refer to <xref
linkend="errcodes-table"> for a list of possible error
codes). The <varname>SQLERRM</varname> variable contains the
error message associated with the exception. These variables are
undefined outside exception handlers.
</para>
<example id="plpgsql-upsert-example"> <example id="plpgsql-upsert-example">
<title>Exceptions with <command>UPDATE</>/<command>INSERT</></title> <title>Exceptions with <command>UPDATE</>/<command>INSERT</></title>
<para> <para>
...@@ -2568,11 +2558,112 @@ LANGUAGE plpgsql; ...@@ -2568,11 +2558,112 @@ LANGUAGE plpgsql;
SELECT merge_db(1, 'david'); SELECT merge_db(1, 'david');
SELECT merge_db(1, 'dennis'); SELECT merge_db(1, 'dennis');
</programlisting> </programlisting>
This example assumes the <literal>unique_violation</> error is caused by
the <command>INSERT</>, and not by an <command>INSERT</> trigger function This coding assumes the <literal>unique_violation</> error is caused by
on the table. the <command>INSERT</>, and not by, say, an <command>INSERT</> in a
trigger function on the table. More safety could be had by using the
features discussed next to check that the trapped error was the one
expected.
</para> </para>
</example> </example>
<sect3 id="plpgsql-exception-diagnostics">
<title>Obtaining information about an error</title>
<para>
Exception handlers frequently need to identify the specific error that
occurred. There are two ways to get information about the current
exception in <application>PL/pgSQL</>: special variables and the
<command>GET STACKED DIAGNOSTICS</command> command.
</para>
<para>
Within an exception handler, the special variable
<varname>SQLSTATE</varname> contains the error code that corresponds to
the exception that was raised (refer to <xref linkend="errcodes-table">
for a list of possible error codes). The special variable
<varname>SQLERRM</varname> contains the error message associated with the
exception. These variables are undefined outside exception handlers.
</para>
<para>
Within an exception handler, one may also retrieve
information about the current exception by using the
<command>GET STACKED DIAGNOSTICS</command> command, which has the form:
<synopsis>
GET STACKED DIAGNOSTICS <replaceable>variable</replaceable> = <replaceable>item</replaceable> <optional> , ... </optional>;
</synopsis>
Each <replaceable>item</replaceable> is a key word identifying a status
value to be assigned to the specified variable (which should be
of the right data type to receive it). The currently available
status items are:
<table id="plpgsql-exception-diagnostics-values">
<title>Error diagnostics values</title>
<tgroup cols="3">
<thead>
<row>
<entry>Name</entry>
<entry>Type</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry><literal>RETURNED_SQLSTATE</literal></entry>
<entry>text</entry>
<entry>the SQLSTATE error code of the exception</entry>
</row>
<row>
<entry><literal>MESSAGE_TEXT</literal></entry>
<entry>text</entry>
<entry>the text of the exception's primary message</entry>
</row>
<row>
<entry><literal>PG_EXCEPTION_DETAIL</literal></entry>
<entry>text</entry>
<entry>the text of the exception's detail message, if any</entry>
</row>
<row>
<entry><literal>PG_EXCEPTION_HINT</literal></entry>
<entry>text</entry>
<entry>the text of the exception's hint message, if any</entry>
</row>
<row>
<entry><literal>PG_EXCEPTION_CONTEXT</literal></entry>
<entry>text</entry>
<entry>line(s) of text describing the call stack</entry>
</row>
</tbody>
</tgroup>
</table>
</para>
<para>
If the exception did not set a value for an item, an empty string
will be returned.
</para>
<para>
Here is an example:
<programlisting>
DECLARE
text_var1 text;
text_var2 text;
text_var3 text;
BEGIN
-- some processing which might cause an exception
...
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS text_var1 = MESSAGE_TEXT,
text_var2 = PG_EXCEPTION_DETAIL,
text_var3 = PG_EXCEPTION_HINT;
END;
</programlisting>
</para>
</sect3>
</sect2> </sect2>
</sect1> </sect1>
......
...@@ -51,7 +51,8 @@ ...@@ -51,7 +51,8 @@
# class (the first two characters of the code value identify the class). # class (the first two characters of the code value identify the class).
# The listing is organized by class to make this prominent. # The listing is organized by class to make this prominent.
# #
# The generic '000' subclass code should be used for an error only # Each class should have a generic '000' subclass. However,
# the generic '000' subclass code should be used for an error only
# when there is not a more-specific subclass code defined. # when there is not a more-specific subclass code defined.
# #
# The SQL spec requires that all the elements of a SQLSTATE code be # The SQL spec requires that all the elements of a SQLSTATE code be
...@@ -132,6 +133,11 @@ Section: Class 0P - Invalid Role Specification ...@@ -132,6 +133,11 @@ Section: Class 0P - Invalid Role Specification
0P000 E ERRCODE_INVALID_ROLE_SPECIFICATION invalid_role_specification 0P000 E ERRCODE_INVALID_ROLE_SPECIFICATION invalid_role_specification
Section: Class 0Z - Diagnostics Exception
0Z000 E ERRCODE_DIAGNOSTICS_EXCEPTION diagnostics_exception
0Z002 E ERRCODE_STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER stacked_diagnostics_accessed_without_active_handler
Section: Class 20 - Case Not Found Section: Class 20 - Case Not Found
20000 E ERRCODE_CASE_NOT_FOUND case_not_found 20000 E ERRCODE_CASE_NOT_FOUND case_not_found
...@@ -399,6 +405,7 @@ Section: Class 57 - Operator Intervention ...@@ -399,6 +405,7 @@ Section: Class 57 - Operator Intervention
Section: Class 58 - System Error (errors external to PostgreSQL itself) Section: Class 58 - System Error (errors external to PostgreSQL itself)
# (class borrowed from DB2) # (class borrowed from DB2)
58000 E ERRCODE_SYSTEM_ERROR system_error
58030 E ERRCODE_IO_ERROR io_error 58030 E ERRCODE_IO_ERROR io_error
58P01 E ERRCODE_UNDEFINED_FILE undefined_file 58P01 E ERRCODE_UNDEFINED_FILE undefined_file
58P02 E ERRCODE_DUPLICATE_FILE duplicate_file 58P02 E ERRCODE_DUPLICATE_FILE duplicate_file
......
...@@ -203,6 +203,7 @@ static List *read_raise_options(void); ...@@ -203,6 +203,7 @@ static List *read_raise_options(void);
%type <casewhen> case_when %type <casewhen> case_when
%type <list> case_when_list opt_case_else %type <list> case_when_list opt_case_else
%type <boolean> getdiag_area_opt
%type <list> getdiag_list %type <list> getdiag_list
%type <diagitem> getdiag_list_item %type <diagitem> getdiag_list_item
%type <ival> getdiag_item getdiag_target %type <ival> getdiag_item getdiag_target
...@@ -251,6 +252,7 @@ static List *read_raise_options(void); ...@@ -251,6 +252,7 @@ static List *read_raise_options(void);
%token <keyword> K_COLLATE %token <keyword> K_COLLATE
%token <keyword> K_CONSTANT %token <keyword> K_CONSTANT
%token <keyword> K_CONTINUE %token <keyword> K_CONTINUE
%token <keyword> K_CURRENT
%token <keyword> K_CURSOR %token <keyword> K_CURSOR
%token <keyword> K_DEBUG %token <keyword> K_DEBUG
%token <keyword> K_DECLARE %token <keyword> K_DECLARE
...@@ -284,6 +286,7 @@ static List *read_raise_options(void); ...@@ -284,6 +286,7 @@ static List *read_raise_options(void);
%token <keyword> K_LOG %token <keyword> K_LOG
%token <keyword> K_LOOP %token <keyword> K_LOOP
%token <keyword> K_MESSAGE %token <keyword> K_MESSAGE
%token <keyword> K_MESSAGE_TEXT
%token <keyword> K_MOVE %token <keyword> K_MOVE
%token <keyword> K_NEXT %token <keyword> K_NEXT
%token <keyword> K_NO %token <keyword> K_NO
...@@ -294,18 +297,23 @@ static List *read_raise_options(void); ...@@ -294,18 +297,23 @@ static List *read_raise_options(void);
%token <keyword> K_OPTION %token <keyword> K_OPTION
%token <keyword> K_OR %token <keyword> K_OR
%token <keyword> K_PERFORM %token <keyword> K_PERFORM
%token <keyword> K_PG_EXCEPTION_CONTEXT
%token <keyword> K_PG_EXCEPTION_DETAIL
%token <keyword> K_PG_EXCEPTION_HINT
%token <keyword> K_PRIOR %token <keyword> K_PRIOR
%token <keyword> K_QUERY %token <keyword> K_QUERY
%token <keyword> K_RAISE %token <keyword> K_RAISE
%token <keyword> K_RELATIVE %token <keyword> K_RELATIVE
%token <keyword> K_RESULT_OID %token <keyword> K_RESULT_OID
%token <keyword> K_RETURN %token <keyword> K_RETURN
%token <keyword> K_RETURNED_SQLSTATE
%token <keyword> K_REVERSE %token <keyword> K_REVERSE
%token <keyword> K_ROWTYPE %token <keyword> K_ROWTYPE
%token <keyword> K_ROW_COUNT %token <keyword> K_ROW_COUNT
%token <keyword> K_SCROLL %token <keyword> K_SCROLL
%token <keyword> K_SLICE %token <keyword> K_SLICE
%token <keyword> K_SQLSTATE %token <keyword> K_SQLSTATE
%token <keyword> K_STACKED
%token <keyword> K_STRICT %token <keyword> K_STRICT
%token <keyword> K_THEN %token <keyword> K_THEN
%token <keyword> K_TO %token <keyword> K_TO
...@@ -832,19 +840,74 @@ stmt_assign : assign_var assign_operator expr_until_semi ...@@ -832,19 +840,74 @@ stmt_assign : assign_var assign_operator expr_until_semi
} }
; ;
stmt_getdiag : K_GET K_DIAGNOSTICS getdiag_list ';' stmt_getdiag : K_GET getdiag_area_opt K_DIAGNOSTICS getdiag_list ';'
{ {
PLpgSQL_stmt_getdiag *new; PLpgSQL_stmt_getdiag *new;
ListCell *lc;
new = palloc0(sizeof(PLpgSQL_stmt_getdiag)); new = palloc0(sizeof(PLpgSQL_stmt_getdiag));
new->cmd_type = PLPGSQL_STMT_GETDIAG; new->cmd_type = PLPGSQL_STMT_GETDIAG;
new->lineno = plpgsql_location_to_lineno(@1); new->lineno = plpgsql_location_to_lineno(@1);
new->diag_items = $3; new->is_stacked = $2;
new->diag_items = $4;
/*
* Check information items are valid for area option.
*/
foreach(lc, new->diag_items)
{
PLpgSQL_diag_item *ditem = (PLpgSQL_diag_item *) lfirst(lc);
switch (ditem->kind)
{
/* these fields are disallowed in stacked case */
case PLPGSQL_GETDIAG_ROW_COUNT:
case PLPGSQL_GETDIAG_RESULT_OID:
if (new->is_stacked)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS",
plpgsql_getdiag_kindname(ditem->kind)),
parser_errposition(@1)));
break;
/* these fields are disallowed in current case */
case PLPGSQL_GETDIAG_ERROR_CONTEXT:
case PLPGSQL_GETDIAG_ERROR_DETAIL:
case PLPGSQL_GETDIAG_ERROR_HINT:
case PLPGSQL_GETDIAG_RETURNED_SQLSTATE:
case PLPGSQL_GETDIAG_MESSAGE_TEXT:
if (!new->is_stacked)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS",
plpgsql_getdiag_kindname(ditem->kind)),
parser_errposition(@1)));
break;
default:
elog(ERROR, "unrecognized diagnostic item kind: %d",
ditem->kind);
break;
}
}
$$ = (PLpgSQL_stmt *)new; $$ = (PLpgSQL_stmt *)new;
} }
; ;
getdiag_area_opt :
{
$$ = false;
}
| K_CURRENT
{
$$ = false;
}
| K_STACKED
{
$$ = true;
}
;
getdiag_list : getdiag_list ',' getdiag_list_item getdiag_list : getdiag_list ',' getdiag_list_item
{ {
$$ = lappend($1, $3); $$ = lappend($1, $3);
...@@ -877,6 +940,21 @@ getdiag_item : ...@@ -877,6 +940,21 @@ getdiag_item :
else if (tok_is_keyword(tok, &yylval, else if (tok_is_keyword(tok, &yylval,
K_RESULT_OID, "result_oid")) K_RESULT_OID, "result_oid"))
$$ = PLPGSQL_GETDIAG_RESULT_OID; $$ = PLPGSQL_GETDIAG_RESULT_OID;
else if (tok_is_keyword(tok, &yylval,
K_PG_EXCEPTION_DETAIL, "pg_exception_detail"))
$$ = PLPGSQL_GETDIAG_ERROR_DETAIL;
else if (tok_is_keyword(tok, &yylval,
K_PG_EXCEPTION_HINT, "pg_exception_hint"))
$$ = PLPGSQL_GETDIAG_ERROR_HINT;
else if (tok_is_keyword(tok, &yylval,
K_PG_EXCEPTION_CONTEXT, "pg_exception_context"))
$$ = PLPGSQL_GETDIAG_ERROR_CONTEXT;
else if (tok_is_keyword(tok, &yylval,
K_MESSAGE_TEXT, "message_text"))
$$ = PLPGSQL_GETDIAG_MESSAGE_TEXT;
else if (tok_is_keyword(tok, &yylval,
K_RETURNED_SQLSTATE, "returned_sqlstate"))
$$ = PLPGSQL_GETDIAG_RETURNED_SQLSTATE;
else else
yyerror("unrecognized GET DIAGNOSTICS item"); yyerror("unrecognized GET DIAGNOSTICS item");
} }
...@@ -2135,6 +2213,7 @@ unreserved_keyword : ...@@ -2135,6 +2213,7 @@ unreserved_keyword :
| K_ARRAY | K_ARRAY
| K_BACKWARD | K_BACKWARD
| K_CONSTANT | K_CONSTANT
| K_CURRENT
| K_CURSOR | K_CURSOR
| K_DEBUG | K_DEBUG
| K_DETAIL | K_DETAIL
...@@ -2149,20 +2228,26 @@ unreserved_keyword : ...@@ -2149,20 +2228,26 @@ unreserved_keyword :
| K_LAST | K_LAST
| K_LOG | K_LOG
| K_MESSAGE | K_MESSAGE
| K_MESSAGE_TEXT
| K_NEXT | K_NEXT
| K_NO | K_NO
| K_NOTICE | K_NOTICE
| K_OPTION | K_OPTION
| K_PG_EXCEPTION_CONTEXT
| K_PG_EXCEPTION_DETAIL
| K_PG_EXCEPTION_HINT
| K_PRIOR | K_PRIOR
| K_QUERY | K_QUERY
| K_RELATIVE | K_RELATIVE
| K_RESULT_OID | K_RESULT_OID
| K_RETURNED_SQLSTATE
| K_REVERSE | K_REVERSE
| K_ROW_COUNT | K_ROW_COUNT
| K_ROWTYPE | K_ROWTYPE
| K_SCROLL | K_SCROLL
| K_SLICE | K_SLICE
| K_SQLSTATE | K_SQLSTATE
| K_STACKED
| K_TYPE | K_TYPE
| K_USE_COLUMN | K_USE_COLUMN
| K_USE_VARIABLE | K_USE_VARIABLE
......
...@@ -151,6 +151,9 @@ static bool exec_eval_simple_expr(PLpgSQL_execstate *estate, ...@@ -151,6 +151,9 @@ static bool exec_eval_simple_expr(PLpgSQL_execstate *estate,
static void exec_assign_expr(PLpgSQL_execstate *estate, static void exec_assign_expr(PLpgSQL_execstate *estate,
PLpgSQL_datum *target, PLpgSQL_datum *target,
PLpgSQL_expr *expr); PLpgSQL_expr *expr);
static void exec_assign_c_string(PLpgSQL_execstate *estate,
PLpgSQL_datum *target,
const char *str);
static void exec_assign_value(PLpgSQL_execstate *estate, static void exec_assign_value(PLpgSQL_execstate *estate,
PLpgSQL_datum *target, PLpgSQL_datum *target,
Datum value, Oid valtype, bool *isNull); Datum value, Oid valtype, bool *isNull);
...@@ -1421,6 +1424,17 @@ exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt) ...@@ -1421,6 +1424,17 @@ exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt)
{ {
ListCell *lc; ListCell *lc;
/*
* GET STACKED DIAGNOSTICS is only valid inside an exception handler.
*
* Note: we trust the grammar to have disallowed the relevant item kinds
* if not is_stacked, otherwise we'd dump core below.
*/
if (stmt->is_stacked && estate->cur_error == NULL)
ereport(ERROR,
(errcode(ERRCODE_STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER),
errmsg("GET STACKED DIAGNOSTICS cannot be used outside an exception handler")));
foreach(lc, stmt->diag_items) foreach(lc, stmt->diag_items)
{ {
PLpgSQL_diag_item *diag_item = (PLpgSQL_diag_item *) lfirst(lc); PLpgSQL_diag_item *diag_item = (PLpgSQL_diag_item *) lfirst(lc);
...@@ -1438,21 +1452,44 @@ exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt) ...@@ -1438,21 +1452,44 @@ exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt)
switch (diag_item->kind) switch (diag_item->kind)
{ {
case PLPGSQL_GETDIAG_ROW_COUNT: case PLPGSQL_GETDIAG_ROW_COUNT:
exec_assign_value(estate, var, exec_assign_value(estate, var,
UInt32GetDatum(estate->eval_processed), UInt32GetDatum(estate->eval_processed),
INT4OID, &isnull); INT4OID, &isnull);
break; break;
case PLPGSQL_GETDIAG_RESULT_OID: case PLPGSQL_GETDIAG_RESULT_OID:
exec_assign_value(estate, var, exec_assign_value(estate, var,
ObjectIdGetDatum(estate->eval_lastoid), ObjectIdGetDatum(estate->eval_lastoid),
OIDOID, &isnull); OIDOID, &isnull);
break; break;
case PLPGSQL_GETDIAG_ERROR_CONTEXT:
exec_assign_c_string(estate, var,
estate->cur_error->context);
break;
case PLPGSQL_GETDIAG_ERROR_DETAIL:
exec_assign_c_string(estate, var,
estate->cur_error->detail);
break;
case PLPGSQL_GETDIAG_ERROR_HINT:
exec_assign_c_string(estate, var,
estate->cur_error->hint);
break;
case PLPGSQL_GETDIAG_RETURNED_SQLSTATE:
exec_assign_c_string(estate, var,
unpack_sql_state(estate->cur_error->sqlerrcode));
break;
case PLPGSQL_GETDIAG_MESSAGE_TEXT:
exec_assign_c_string(estate, var,
estate->cur_error->message);
break;
default: default:
elog(ERROR, "unrecognized attribute request: %d", elog(ERROR, "unrecognized diagnostic item kind: %d",
diag_item->kind); diag_item->kind);
} }
} }
...@@ -2634,7 +2671,7 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt) ...@@ -2634,7 +2671,7 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
ReThrowError(estate->cur_error); ReThrowError(estate->cur_error);
/* oops, we're not inside a handler */ /* oops, we're not inside a handler */
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER),
errmsg("RAISE without parameters cannot be used outside an exception handler"))); errmsg("RAISE without parameters cannot be used outside an exception handler")));
} }
...@@ -3650,8 +3687,7 @@ exec_stmt_close(PLpgSQL_execstate *estate, PLpgSQL_stmt_close *stmt) ...@@ -3650,8 +3687,7 @@ exec_stmt_close(PLpgSQL_execstate *estate, PLpgSQL_stmt_close *stmt)
/* ---------- /* ----------
* exec_assign_expr Put an expression's result into * exec_assign_expr Put an expression's result into a variable.
* a variable.
* ---------- * ----------
*/ */
static void static void
...@@ -3668,6 +3704,29 @@ exec_assign_expr(PLpgSQL_execstate *estate, PLpgSQL_datum *target, ...@@ -3668,6 +3704,29 @@ exec_assign_expr(PLpgSQL_execstate *estate, PLpgSQL_datum *target,
} }
/* ----------
* exec_assign_c_string Put a C string into a text variable.
*
* We take a NULL pointer as signifying empty string, not SQL null.
* ----------
*/
static void
exec_assign_c_string(PLpgSQL_execstate *estate, PLpgSQL_datum *target,
const char *str)
{
text *value;
bool isnull = false;
if (str != NULL)
value = cstring_to_text(str);
else
value = cstring_to_text("");
exec_assign_value(estate, target, PointerGetDatum(value),
TEXTOID, &isnull);
pfree(value);
}
/* ---------- /* ----------
* exec_assign_value Put a value into a target field * exec_assign_value Put a value into a target field
* *
......
...@@ -265,6 +265,33 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt) ...@@ -265,6 +265,33 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
return "unknown"; return "unknown";
} }
/*
* GET DIAGNOSTICS item name as a string, for use in error messages etc.
*/
const char *
plpgsql_getdiag_kindname(int kind)
{
switch (kind)
{
case PLPGSQL_GETDIAG_ROW_COUNT:
return "ROW_COUNT";
case PLPGSQL_GETDIAG_RESULT_OID:
return "RESULT_OID";
case PLPGSQL_GETDIAG_ERROR_CONTEXT:
return "PG_EXCEPTION_CONTEXT";
case PLPGSQL_GETDIAG_ERROR_DETAIL:
return "PG_EXCEPTION_DETAIL";
case PLPGSQL_GETDIAG_ERROR_HINT:
return "PG_EXCEPTION_HINT";
case PLPGSQL_GETDIAG_RETURNED_SQLSTATE:
return "RETURNED_SQLSTATE";
case PLPGSQL_GETDIAG_MESSAGE_TEXT:
return "MESSAGE_TEXT";
}
return "unknown";
}
/********************************************************************** /**********************************************************************
* Release memory when a PL/pgSQL function is no longer needed * Release memory when a PL/pgSQL function is no longer needed
...@@ -1389,7 +1416,7 @@ dump_getdiag(PLpgSQL_stmt_getdiag *stmt) ...@@ -1389,7 +1416,7 @@ dump_getdiag(PLpgSQL_stmt_getdiag *stmt)
ListCell *lc; ListCell *lc;
dump_ind(); dump_ind();
printf("GET DIAGNOSTICS "); printf("GET %s DIAGNOSTICS ", stmt->is_stacked ? "STACKED" : "CURRENT");
foreach(lc, stmt->diag_items) foreach(lc, stmt->diag_items)
{ {
PLpgSQL_diag_item *diag_item = (PLpgSQL_diag_item *) lfirst(lc); PLpgSQL_diag_item *diag_item = (PLpgSQL_diag_item *) lfirst(lc);
...@@ -1397,22 +1424,8 @@ dump_getdiag(PLpgSQL_stmt_getdiag *stmt) ...@@ -1397,22 +1424,8 @@ dump_getdiag(PLpgSQL_stmt_getdiag *stmt)
if (lc != list_head(stmt->diag_items)) if (lc != list_head(stmt->diag_items))
printf(", "); printf(", ");
printf("{var %d} = ", diag_item->target); printf("{var %d} = %s", diag_item->target,
plpgsql_getdiag_kindname(diag_item->kind));
switch (diag_item->kind)
{
case PLPGSQL_GETDIAG_ROW_COUNT:
printf("ROW_COUNT");
break;
case PLPGSQL_GETDIAG_RESULT_OID:
printf("RESULT_OID");
break;
default:
printf("???");
break;
}
} }
printf("\n"); printf("\n");
} }
......
...@@ -110,6 +110,7 @@ static const ScanKeyword unreserved_keywords[] = { ...@@ -110,6 +110,7 @@ static const ScanKeyword unreserved_keywords[] = {
PG_KEYWORD("array", K_ARRAY, UNRESERVED_KEYWORD) PG_KEYWORD("array", K_ARRAY, UNRESERVED_KEYWORD)
PG_KEYWORD("backward", K_BACKWARD, UNRESERVED_KEYWORD) PG_KEYWORD("backward", K_BACKWARD, UNRESERVED_KEYWORD)
PG_KEYWORD("constant", K_CONSTANT, UNRESERVED_KEYWORD) PG_KEYWORD("constant", K_CONSTANT, UNRESERVED_KEYWORD)
PG_KEYWORD("current", K_CURRENT, UNRESERVED_KEYWORD)
PG_KEYWORD("cursor", K_CURSOR, UNRESERVED_KEYWORD) PG_KEYWORD("cursor", K_CURSOR, UNRESERVED_KEYWORD)
PG_KEYWORD("debug", K_DEBUG, UNRESERVED_KEYWORD) PG_KEYWORD("debug", K_DEBUG, UNRESERVED_KEYWORD)
PG_KEYWORD("detail", K_DETAIL, UNRESERVED_KEYWORD) PG_KEYWORD("detail", K_DETAIL, UNRESERVED_KEYWORD)
...@@ -124,20 +125,26 @@ static const ScanKeyword unreserved_keywords[] = { ...@@ -124,20 +125,26 @@ static const ScanKeyword unreserved_keywords[] = {
PG_KEYWORD("last", K_LAST, UNRESERVED_KEYWORD) PG_KEYWORD("last", K_LAST, UNRESERVED_KEYWORD)
PG_KEYWORD("log", K_LOG, UNRESERVED_KEYWORD) PG_KEYWORD("log", K_LOG, UNRESERVED_KEYWORD)
PG_KEYWORD("message", K_MESSAGE, UNRESERVED_KEYWORD) PG_KEYWORD("message", K_MESSAGE, UNRESERVED_KEYWORD)
PG_KEYWORD("message_text", K_MESSAGE_TEXT, UNRESERVED_KEYWORD)
PG_KEYWORD("next", K_NEXT, UNRESERVED_KEYWORD) PG_KEYWORD("next", K_NEXT, UNRESERVED_KEYWORD)
PG_KEYWORD("no", K_NO, UNRESERVED_KEYWORD) PG_KEYWORD("no", K_NO, UNRESERVED_KEYWORD)
PG_KEYWORD("notice", K_NOTICE, UNRESERVED_KEYWORD) PG_KEYWORD("notice", K_NOTICE, UNRESERVED_KEYWORD)
PG_KEYWORD("option", K_OPTION, UNRESERVED_KEYWORD) PG_KEYWORD("option", K_OPTION, UNRESERVED_KEYWORD)
PG_KEYWORD("pg_exception_context", K_PG_EXCEPTION_CONTEXT, UNRESERVED_KEYWORD)
PG_KEYWORD("pg_exception_detail", K_PG_EXCEPTION_DETAIL, UNRESERVED_KEYWORD)
PG_KEYWORD("pg_exception_hint", K_PG_EXCEPTION_HINT, UNRESERVED_KEYWORD)
PG_KEYWORD("prior", K_PRIOR, UNRESERVED_KEYWORD) PG_KEYWORD("prior", K_PRIOR, UNRESERVED_KEYWORD)
PG_KEYWORD("query", K_QUERY, UNRESERVED_KEYWORD) PG_KEYWORD("query", K_QUERY, UNRESERVED_KEYWORD)
PG_KEYWORD("relative", K_RELATIVE, UNRESERVED_KEYWORD) PG_KEYWORD("relative", K_RELATIVE, UNRESERVED_KEYWORD)
PG_KEYWORD("result_oid", K_RESULT_OID, UNRESERVED_KEYWORD) PG_KEYWORD("result_oid", K_RESULT_OID, UNRESERVED_KEYWORD)
PG_KEYWORD("returned_sqlstate", K_RETURNED_SQLSTATE, UNRESERVED_KEYWORD)
PG_KEYWORD("reverse", K_REVERSE, UNRESERVED_KEYWORD) PG_KEYWORD("reverse", K_REVERSE, UNRESERVED_KEYWORD)
PG_KEYWORD("row_count", K_ROW_COUNT, UNRESERVED_KEYWORD) PG_KEYWORD("row_count", K_ROW_COUNT, UNRESERVED_KEYWORD)
PG_KEYWORD("rowtype", K_ROWTYPE, UNRESERVED_KEYWORD) PG_KEYWORD("rowtype", K_ROWTYPE, UNRESERVED_KEYWORD)
PG_KEYWORD("scroll", K_SCROLL, UNRESERVED_KEYWORD) PG_KEYWORD("scroll", K_SCROLL, UNRESERVED_KEYWORD)
PG_KEYWORD("slice", K_SLICE, UNRESERVED_KEYWORD) PG_KEYWORD("slice", K_SLICE, UNRESERVED_KEYWORD)
PG_KEYWORD("sqlstate", K_SQLSTATE, UNRESERVED_KEYWORD) PG_KEYWORD("sqlstate", K_SQLSTATE, UNRESERVED_KEYWORD)
PG_KEYWORD("stacked", K_STACKED, UNRESERVED_KEYWORD)
PG_KEYWORD("type", K_TYPE, UNRESERVED_KEYWORD) PG_KEYWORD("type", K_TYPE, UNRESERVED_KEYWORD)
PG_KEYWORD("use_column", K_USE_COLUMN, UNRESERVED_KEYWORD) PG_KEYWORD("use_column", K_USE_COLUMN, UNRESERVED_KEYWORD)
PG_KEYWORD("use_variable", K_USE_VARIABLE, UNRESERVED_KEYWORD) PG_KEYWORD("use_variable", K_USE_VARIABLE, UNRESERVED_KEYWORD)
......
...@@ -120,13 +120,18 @@ enum ...@@ -120,13 +120,18 @@ enum
}; };
/* ---------- /* ----------
* GET DIAGNOSTICS system attrs * GET DIAGNOSTICS information items
* ---------- * ----------
*/ */
enum enum
{ {
PLPGSQL_GETDIAG_ROW_COUNT, PLPGSQL_GETDIAG_ROW_COUNT,
PLPGSQL_GETDIAG_RESULT_OID PLPGSQL_GETDIAG_RESULT_OID,
PLPGSQL_GETDIAG_ERROR_CONTEXT,
PLPGSQL_GETDIAG_ERROR_DETAIL,
PLPGSQL_GETDIAG_ERROR_HINT,
PLPGSQL_GETDIAG_RETURNED_SQLSTATE,
PLPGSQL_GETDIAG_MESSAGE_TEXT
}; };
/* -------- /* --------
...@@ -376,6 +381,7 @@ typedef struct ...@@ -376,6 +381,7 @@ typedef struct
{ /* Get Diagnostics statement */ { /* Get Diagnostics statement */
int cmd_type; int cmd_type;
int lineno; int lineno;
bool is_stacked; /* STACKED or CURRENT diagnostics area? */
List *diag_items; /* List of PLpgSQL_diag_item */ List *diag_items; /* List of PLpgSQL_diag_item */
} PLpgSQL_stmt_getdiag; } PLpgSQL_stmt_getdiag;
...@@ -929,6 +935,7 @@ extern PLpgSQL_nsitem *plpgsql_ns_lookup_label(PLpgSQL_nsitem *ns_cur, ...@@ -929,6 +935,7 @@ extern PLpgSQL_nsitem *plpgsql_ns_lookup_label(PLpgSQL_nsitem *ns_cur,
* ---------- * ----------
*/ */
extern const char *plpgsql_stmt_typename(PLpgSQL_stmt *stmt); extern const char *plpgsql_stmt_typename(PLpgSQL_stmt *stmt);
extern const char *plpgsql_getdiag_kindname(int kind);
extern void plpgsql_free_function_memory(PLpgSQL_function *func); extern void plpgsql_free_function_memory(PLpgSQL_function *func);
extern void plpgsql_dumptree(PLpgSQL_function *func); extern void plpgsql_dumptree(PLpgSQL_function *func);
......
...@@ -3607,6 +3607,81 @@ $$ language plpgsql; ...@@ -3607,6 +3607,81 @@ $$ language plpgsql;
select raise_test(); select raise_test();
ERROR: RAISE without parameters cannot be used outside an exception handler ERROR: RAISE without parameters cannot be used outside an exception handler
CONTEXT: PL/pgSQL function "raise_test" line 3 at RAISE CONTEXT: PL/pgSQL function "raise_test" line 3 at RAISE
-- test access to exception data
create function zero_divide() returns int as $$
declare v int := 0;
begin
return 10 / v;
end;
$$ language plpgsql;
create or replace function raise_test() returns void as $$
begin
raise exception 'custom exception'
using detail = 'some detail of custom exception',
hint = 'some hint related to custom exception';
end;
$$ language plpgsql;
create function stacked_diagnostics_test() returns void as $$
declare _sqlstate text;
_message text;
_context text;
begin
perform zero_divide();
exception when others then
get stacked diagnostics
_sqlstate = returned_sqlstate,
_message = message_text,
_context = pg_exception_context;
raise notice 'sqlstate: %, message: %, context: [%]',
_sqlstate, _message, replace(_context, E'\n', ' <- ');
end;
$$ language plpgsql;
select stacked_diagnostics_test();
NOTICE: sqlstate: 22012, message: division by zero, context: [PL/pgSQL function "zero_divide" line 4 at RETURN <- SQL statement "SELECT zero_divide()" <- PL/pgSQL function "stacked_diagnostics_test" line 6 at PERFORM]
stacked_diagnostics_test
--------------------------
(1 row)
create or replace function stacked_diagnostics_test() returns void as $$
declare _detail text;
_hint text;
_message text;
begin
perform raise_test();
exception when others then
get stacked diagnostics
_message = message_text,
_detail = pg_exception_detail,
_hint = pg_exception_hint;
raise notice 'message: %, detail: %, hint: %', _message, _detail, _hint;
end;
$$ language plpgsql;
select stacked_diagnostics_test();
NOTICE: message: custom exception, detail: some detail of custom exception, hint: some hint related to custom exception
stacked_diagnostics_test
--------------------------
(1 row)
-- fail, cannot use stacked diagnostics statement outside handler
create or replace function stacked_diagnostics_test() returns void as $$
declare _detail text;
_hint text;
_message text;
begin
get stacked diagnostics
_message = message_text,
_detail = pg_exception_detail,
_hint = pg_exception_hint;
raise notice 'message: %, detail: %, hint: %', _message, _detail, _hint;
end;
$$ language plpgsql;
select stacked_diagnostics_test();
ERROR: GET STACKED DIAGNOSTICS cannot be used outside an exception handler
CONTEXT: PL/pgSQL function "stacked_diagnostics_test" line 6 at GET DIAGNOSTICS
drop function zero_divide();
drop function stacked_diagnostics_test();
-- check cases where implicit SQLSTATE variable could be confused with -- check cases where implicit SQLSTATE variable could be confused with
-- SQLSTATE as a keyword, cf bug #5524 -- SQLSTATE as a keyword, cf bug #5524
create or replace function raise_test() returns void as $$ create or replace function raise_test() returns void as $$
......
...@@ -2941,6 +2941,76 @@ $$ language plpgsql; ...@@ -2941,6 +2941,76 @@ $$ language plpgsql;
select raise_test(); select raise_test();
-- test access to exception data
create function zero_divide() returns int as $$
declare v int := 0;
begin
return 10 / v;
end;
$$ language plpgsql;
create or replace function raise_test() returns void as $$
begin
raise exception 'custom exception'
using detail = 'some detail of custom exception',
hint = 'some hint related to custom exception';
end;
$$ language plpgsql;
create function stacked_diagnostics_test() returns void as $$
declare _sqlstate text;
_message text;
_context text;
begin
perform zero_divide();
exception when others then
get stacked diagnostics
_sqlstate = returned_sqlstate,
_message = message_text,
_context = pg_exception_context;
raise notice 'sqlstate: %, message: %, context: [%]',
_sqlstate, _message, replace(_context, E'\n', ' <- ');
end;
$$ language plpgsql;
select stacked_diagnostics_test();
create or replace function stacked_diagnostics_test() returns void as $$
declare _detail text;
_hint text;
_message text;
begin
perform raise_test();
exception when others then
get stacked diagnostics
_message = message_text,
_detail = pg_exception_detail,
_hint = pg_exception_hint;
raise notice 'message: %, detail: %, hint: %', _message, _detail, _hint;
end;
$$ language plpgsql;
select stacked_diagnostics_test();
-- fail, cannot use stacked diagnostics statement outside handler
create or replace function stacked_diagnostics_test() returns void as $$
declare _detail text;
_hint text;
_message text;
begin
get stacked diagnostics
_message = message_text,
_detail = pg_exception_detail,
_hint = pg_exception_hint;
raise notice 'message: %, detail: %, hint: %', _message, _detail, _hint;
end;
$$ language plpgsql;
select stacked_diagnostics_test();
drop function zero_divide();
drop function stacked_diagnostics_test();
-- check cases where implicit SQLSTATE variable could be confused with -- check cases where implicit SQLSTATE variable could be confused with
-- SQLSTATE as a keyword, cf bug #5524 -- SQLSTATE as a keyword, cf bug #5524
create or replace function raise_test() returns void as $$ create or replace function raise_test() returns void as $$
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment