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
This commit is contained in:
Tom Lane 2011-07-18 14:46:27 -04:00
parent 3406dd22fd
commit 3d4890c0c5
9 changed files with 480 additions and 66 deletions

View file

@ -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;
</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;
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>

View file

@ -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
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
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
@ -415,30 +422,30 @@ Section: Class HV - Foreign Data Wrapper Error (SQL/MED)
HV000 E ERRCODE_FDW_ERROR fdw_error HV000 E ERRCODE_FDW_ERROR fdw_error
HV005 E ERRCODE_FDW_COLUMN_NAME_NOT_FOUND fdw_column_name_not_found HV005 E ERRCODE_FDW_COLUMN_NAME_NOT_FOUND fdw_column_name_not_found
HV002 E ERRCODE_FDW_DYNAMIC_PARAMETER_VALUE_NEEDED fdw_dynamic_parameter_value_needed HV002 E ERRCODE_FDW_DYNAMIC_PARAMETER_VALUE_NEEDED fdw_dynamic_parameter_value_needed
HV010 E ERRCODE_FDW_FUNCTION_SEQUENCE_ERROR fdw_function_sequence_error HV010 E ERRCODE_FDW_FUNCTION_SEQUENCE_ERROR fdw_function_sequence_error
HV021 E ERRCODE_FDW_INCONSISTENT_DESCRIPTOR_INFORMATION fdw_inconsistent_descriptor_information HV021 E ERRCODE_FDW_INCONSISTENT_DESCRIPTOR_INFORMATION fdw_inconsistent_descriptor_information
HV024 E ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE fdw_invalid_attribute_value HV024 E ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE fdw_invalid_attribute_value
HV007 E ERRCODE_FDW_INVALID_COLUMN_NAME fdw_invalid_column_name HV007 E ERRCODE_FDW_INVALID_COLUMN_NAME fdw_invalid_column_name
HV008 E ERRCODE_FDW_INVALID_COLUMN_NUMBER fdw_invalid_column_number HV008 E ERRCODE_FDW_INVALID_COLUMN_NUMBER fdw_invalid_column_number
HV004 E ERRCODE_FDW_INVALID_DATA_TYPE fdw_invalid_data_type HV004 E ERRCODE_FDW_INVALID_DATA_TYPE fdw_invalid_data_type
HV006 E ERRCODE_FDW_INVALID_DATA_TYPE_DESCRIPTORS fdw_invalid_data_type_descriptors HV006 E ERRCODE_FDW_INVALID_DATA_TYPE_DESCRIPTORS fdw_invalid_data_type_descriptors
HV091 E ERRCODE_FDW_INVALID_DESCRIPTOR_FIELD_IDENTIFIER fdw_invalid_descriptor_field_identifier HV091 E ERRCODE_FDW_INVALID_DESCRIPTOR_FIELD_IDENTIFIER fdw_invalid_descriptor_field_identifier
HV00B E ERRCODE_FDW_INVALID_HANDLE fdw_invalid_handle HV00B E ERRCODE_FDW_INVALID_HANDLE fdw_invalid_handle
HV00C E ERRCODE_FDW_INVALID_OPTION_INDEX fdw_invalid_option_index HV00C E ERRCODE_FDW_INVALID_OPTION_INDEX fdw_invalid_option_index
HV00D E ERRCODE_FDW_INVALID_OPTION_NAME fdw_invalid_option_name HV00D E ERRCODE_FDW_INVALID_OPTION_NAME fdw_invalid_option_name
HV090 E ERRCODE_FDW_INVALID_STRING_LENGTH_OR_BUFFER_LENGTH fdw_invalid_string_length_or_buffer_length HV090 E ERRCODE_FDW_INVALID_STRING_LENGTH_OR_BUFFER_LENGTH fdw_invalid_string_length_or_buffer_length
HV00A E ERRCODE_FDW_INVALID_STRING_FORMAT fdw_invalid_string_format HV00A E ERRCODE_FDW_INVALID_STRING_FORMAT fdw_invalid_string_format
HV009 E ERRCODE_FDW_INVALID_USE_OF_NULL_POINTER fdw_invalid_use_of_null_pointer HV009 E ERRCODE_FDW_INVALID_USE_OF_NULL_POINTER fdw_invalid_use_of_null_pointer
HV014 E ERRCODE_FDW_TOO_MANY_HANDLES fdw_too_many_handles HV014 E ERRCODE_FDW_TOO_MANY_HANDLES fdw_too_many_handles
HV001 E ERRCODE_FDW_OUT_OF_MEMORY fdw_out_of_memory HV001 E ERRCODE_FDW_OUT_OF_MEMORY fdw_out_of_memory
HV00P E ERRCODE_FDW_NO_SCHEMAS fdw_no_schemas HV00P E ERRCODE_FDW_NO_SCHEMAS fdw_no_schemas
HV00J E ERRCODE_FDW_OPTION_NAME_NOT_FOUND fdw_option_name_not_found HV00J E ERRCODE_FDW_OPTION_NAME_NOT_FOUND fdw_option_name_not_found
HV00K E ERRCODE_FDW_REPLY_HANDLE fdw_reply_handle HV00K E ERRCODE_FDW_REPLY_HANDLE fdw_reply_handle
HV00Q E ERRCODE_FDW_SCHEMA_NOT_FOUND fdw_schema_not_found HV00Q E ERRCODE_FDW_SCHEMA_NOT_FOUND fdw_schema_not_found
HV00R E ERRCODE_FDW_TABLE_NOT_FOUND fdw_table_not_found HV00R E ERRCODE_FDW_TABLE_NOT_FOUND fdw_table_not_found
HV00L E ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION fdw_unable_to_create_execution HV00L E ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION fdw_unable_to_create_execution
HV00M E ERRCODE_FDW_UNABLE_TO_CREATE_REPLY fdw_unable_to_create_reply HV00M E ERRCODE_FDW_UNABLE_TO_CREATE_REPLY fdw_unable_to_create_reply
HV00N E ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION fdw_unable_to_establish_connection HV00N E ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION fdw_unable_to_establish_connection
Section: Class P0 - PL/pgSQL Error Section: Class P0 - PL/pgSQL Error

View file

@ -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);
%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);
%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);
%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
} }
; ;
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 :
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 :
| 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 :
| 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

View file

@ -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)
{ {
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)
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)
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)
/* ---------- /* ----------
* 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,
} }
/* ----------
* 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
* *

View file

@ -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)
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)
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");
} }

View file

@ -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[] = {
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)

View file

@ -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
{ /* 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,
* ---------- * ----------
*/ */
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);

View file

@ -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 $$

View file

@ -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 $$