mirror of
https://github.com/postgres/postgres.git
synced 2025-04-21 12:05:57 +03:00
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:
parent
3406dd22fd
commit
3d4890c0c5
@ -1387,11 +1387,11 @@ EXECUTE format('UPDATE tbl SET %I = $1 WHERE key = $2', colname)
|
||||
command, which has the form:
|
||||
|
||||
<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>
|
||||
|
||||
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
|
||||
of the right data type to receive it). The currently available
|
||||
status items are <varname>ROW_COUNT</>, the number of rows
|
||||
@ -2522,16 +2522,6 @@ END;
|
||||
</para>
|
||||
</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">
|
||||
<title>Exceptions with <command>UPDATE</>/<command>INSERT</></title>
|
||||
<para>
|
||||
@ -2568,11 +2558,112 @@ LANGUAGE plpgsql;
|
||||
SELECT merge_db(1, 'david');
|
||||
SELECT merge_db(1, 'dennis');
|
||||
</programlisting>
|
||||
This example assumes the <literal>unique_violation</> error is caused by
|
||||
the <command>INSERT</>, and not by an <command>INSERT</> trigger function
|
||||
on the table.
|
||||
|
||||
This coding assumes the <literal>unique_violation</> error is caused by
|
||||
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>
|
||||
</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>
|
||||
</sect1>
|
||||
|
||||
|
@ -51,7 +51,8 @@
|
||||
# class (the first two characters of the code value identify the class).
|
||||
# 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.
|
||||
#
|
||||
# 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
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
# (class borrowed from DB2)
|
||||
58000 E ERRCODE_SYSTEM_ERROR system_error
|
||||
58030 E ERRCODE_IO_ERROR io_error
|
||||
58P01 E ERRCODE_UNDEFINED_FILE undefined_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
|
||||
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
|
||||
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
|
||||
HV024 E ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE fdw_invalid_attribute_value
|
||||
HV007 E ERRCODE_FDW_INVALID_COLUMN_NAME fdw_invalid_column_name
|
||||
HV008 E ERRCODE_FDW_INVALID_COLUMN_NUMBER fdw_invalid_column_number
|
||||
HV004 E ERRCODE_FDW_INVALID_DATA_TYPE fdw_invalid_data_type
|
||||
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
|
||||
HV00B E ERRCODE_FDW_INVALID_HANDLE fdw_invalid_handle
|
||||
HV00C E ERRCODE_FDW_INVALID_OPTION_INDEX fdw_invalid_option_index
|
||||
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
|
||||
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
|
||||
HV014 E ERRCODE_FDW_TOO_MANY_HANDLES fdw_too_many_handles
|
||||
HV001 E ERRCODE_FDW_OUT_OF_MEMORY fdw_out_of_memory
|
||||
HV00P E ERRCODE_FDW_NO_SCHEMAS fdw_no_schemas
|
||||
HV00J E ERRCODE_FDW_OPTION_NAME_NOT_FOUND fdw_option_name_not_found
|
||||
HV00K E ERRCODE_FDW_REPLY_HANDLE fdw_reply_handle
|
||||
HV00Q E ERRCODE_FDW_SCHEMA_NOT_FOUND fdw_schema_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
|
||||
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
|
||||
HV024 E ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE fdw_invalid_attribute_value
|
||||
HV007 E ERRCODE_FDW_INVALID_COLUMN_NAME fdw_invalid_column_name
|
||||
HV008 E ERRCODE_FDW_INVALID_COLUMN_NUMBER fdw_invalid_column_number
|
||||
HV004 E ERRCODE_FDW_INVALID_DATA_TYPE fdw_invalid_data_type
|
||||
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
|
||||
HV00B E ERRCODE_FDW_INVALID_HANDLE fdw_invalid_handle
|
||||
HV00C E ERRCODE_FDW_INVALID_OPTION_INDEX fdw_invalid_option_index
|
||||
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
|
||||
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
|
||||
HV014 E ERRCODE_FDW_TOO_MANY_HANDLES fdw_too_many_handles
|
||||
HV001 E ERRCODE_FDW_OUT_OF_MEMORY fdw_out_of_memory
|
||||
HV00P E ERRCODE_FDW_NO_SCHEMAS fdw_no_schemas
|
||||
HV00J E ERRCODE_FDW_OPTION_NAME_NOT_FOUND fdw_option_name_not_found
|
||||
HV00K E ERRCODE_FDW_REPLY_HANDLE fdw_reply_handle
|
||||
HV00Q E ERRCODE_FDW_SCHEMA_NOT_FOUND fdw_schema_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
|
||||
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
|
||||
|
||||
Section: Class P0 - PL/pgSQL Error
|
||||
|
||||
|
@ -203,6 +203,7 @@ static List *read_raise_options(void);
|
||||
%type <casewhen> case_when
|
||||
%type <list> case_when_list opt_case_else
|
||||
|
||||
%type <boolean> getdiag_area_opt
|
||||
%type <list> getdiag_list
|
||||
%type <diagitem> getdiag_list_item
|
||||
%type <ival> getdiag_item getdiag_target
|
||||
@ -251,6 +252,7 @@ static List *read_raise_options(void);
|
||||
%token <keyword> K_COLLATE
|
||||
%token <keyword> K_CONSTANT
|
||||
%token <keyword> K_CONTINUE
|
||||
%token <keyword> K_CURRENT
|
||||
%token <keyword> K_CURSOR
|
||||
%token <keyword> K_DEBUG
|
||||
%token <keyword> K_DECLARE
|
||||
@ -284,6 +286,7 @@ static List *read_raise_options(void);
|
||||
%token <keyword> K_LOG
|
||||
%token <keyword> K_LOOP
|
||||
%token <keyword> K_MESSAGE
|
||||
%token <keyword> K_MESSAGE_TEXT
|
||||
%token <keyword> K_MOVE
|
||||
%token <keyword> K_NEXT
|
||||
%token <keyword> K_NO
|
||||
@ -294,18 +297,23 @@ static List *read_raise_options(void);
|
||||
%token <keyword> K_OPTION
|
||||
%token <keyword> K_OR
|
||||
%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_QUERY
|
||||
%token <keyword> K_RAISE
|
||||
%token <keyword> K_RELATIVE
|
||||
%token <keyword> K_RESULT_OID
|
||||
%token <keyword> K_RETURN
|
||||
%token <keyword> K_RETURNED_SQLSTATE
|
||||
%token <keyword> K_REVERSE
|
||||
%token <keyword> K_ROWTYPE
|
||||
%token <keyword> K_ROW_COUNT
|
||||
%token <keyword> K_SCROLL
|
||||
%token <keyword> K_SLICE
|
||||
%token <keyword> K_SQLSTATE
|
||||
%token <keyword> K_STACKED
|
||||
%token <keyword> K_STRICT
|
||||
%token <keyword> K_THEN
|
||||
%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;
|
||||
ListCell *lc;
|
||||
|
||||
new = palloc0(sizeof(PLpgSQL_stmt_getdiag));
|
||||
new->cmd_type = PLPGSQL_STMT_GETDIAG;
|
||||
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;
|
||||
}
|
||||
;
|
||||
|
||||
getdiag_area_opt :
|
||||
{
|
||||
$$ = false;
|
||||
}
|
||||
| K_CURRENT
|
||||
{
|
||||
$$ = false;
|
||||
}
|
||||
| K_STACKED
|
||||
{
|
||||
$$ = true;
|
||||
}
|
||||
;
|
||||
|
||||
getdiag_list : getdiag_list ',' getdiag_list_item
|
||||
{
|
||||
$$ = lappend($1, $3);
|
||||
@ -877,6 +940,21 @@ getdiag_item :
|
||||
else if (tok_is_keyword(tok, &yylval,
|
||||
K_RESULT_OID, "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
|
||||
yyerror("unrecognized GET DIAGNOSTICS item");
|
||||
}
|
||||
@ -2135,6 +2213,7 @@ unreserved_keyword :
|
||||
| K_ARRAY
|
||||
| K_BACKWARD
|
||||
| K_CONSTANT
|
||||
| K_CURRENT
|
||||
| K_CURSOR
|
||||
| K_DEBUG
|
||||
| K_DETAIL
|
||||
@ -2149,20 +2228,26 @@ unreserved_keyword :
|
||||
| K_LAST
|
||||
| K_LOG
|
||||
| K_MESSAGE
|
||||
| K_MESSAGE_TEXT
|
||||
| K_NEXT
|
||||
| K_NO
|
||||
| K_NOTICE
|
||||
| K_OPTION
|
||||
| K_PG_EXCEPTION_CONTEXT
|
||||
| K_PG_EXCEPTION_DETAIL
|
||||
| K_PG_EXCEPTION_HINT
|
||||
| K_PRIOR
|
||||
| K_QUERY
|
||||
| K_RELATIVE
|
||||
| K_RESULT_OID
|
||||
| K_RETURNED_SQLSTATE
|
||||
| K_REVERSE
|
||||
| K_ROW_COUNT
|
||||
| K_ROWTYPE
|
||||
| K_SCROLL
|
||||
| K_SLICE
|
||||
| K_SQLSTATE
|
||||
| K_STACKED
|
||||
| K_TYPE
|
||||
| K_USE_COLUMN
|
||||
| K_USE_VARIABLE
|
||||
|
@ -151,6 +151,9 @@ static bool exec_eval_simple_expr(PLpgSQL_execstate *estate,
|
||||
static void exec_assign_expr(PLpgSQL_execstate *estate,
|
||||
PLpgSQL_datum *target,
|
||||
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,
|
||||
PLpgSQL_datum *target,
|
||||
Datum value, Oid valtype, bool *isNull);
|
||||
@ -1421,6 +1424,17 @@ exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
case PLPGSQL_GETDIAG_ROW_COUNT:
|
||||
|
||||
exec_assign_value(estate, var,
|
||||
UInt32GetDatum(estate->eval_processed),
|
||||
INT4OID, &isnull);
|
||||
break;
|
||||
|
||||
case PLPGSQL_GETDIAG_RESULT_OID:
|
||||
|
||||
exec_assign_value(estate, var,
|
||||
ObjectIdGetDatum(estate->eval_lastoid),
|
||||
OIDOID, &isnull);
|
||||
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:
|
||||
elog(ERROR, "unrecognized attribute request: %d",
|
||||
elog(ERROR, "unrecognized diagnostic item kind: %d",
|
||||
diag_item->kind);
|
||||
}
|
||||
}
|
||||
@ -2634,7 +2671,7 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
|
||||
ReThrowError(estate->cur_error);
|
||||
/* oops, we're not inside a handler */
|
||||
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")));
|
||||
}
|
||||
|
||||
@ -3650,8 +3687,7 @@ exec_stmt_close(PLpgSQL_execstate *estate, PLpgSQL_stmt_close *stmt)
|
||||
|
||||
|
||||
/* ----------
|
||||
* exec_assign_expr Put an expression's result into
|
||||
* a variable.
|
||||
* exec_assign_expr Put an expression's result into a variable.
|
||||
* ----------
|
||||
*/
|
||||
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
|
||||
*
|
||||
|
@ -265,6 +265,33 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
|
||||
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
|
||||
@ -1389,7 +1416,7 @@ dump_getdiag(PLpgSQL_stmt_getdiag *stmt)
|
||||
ListCell *lc;
|
||||
|
||||
dump_ind();
|
||||
printf("GET DIAGNOSTICS ");
|
||||
printf("GET %s DIAGNOSTICS ", stmt->is_stacked ? "STACKED" : "CURRENT");
|
||||
foreach(lc, stmt->diag_items)
|
||||
{
|
||||
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))
|
||||
printf(", ");
|
||||
|
||||
printf("{var %d} = ", diag_item->target);
|
||||
|
||||
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("{var %d} = %s", diag_item->target,
|
||||
plpgsql_getdiag_kindname(diag_item->kind));
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
@ -110,6 +110,7 @@ static const ScanKeyword unreserved_keywords[] = {
|
||||
PG_KEYWORD("array", K_ARRAY, UNRESERVED_KEYWORD)
|
||||
PG_KEYWORD("backward", K_BACKWARD, 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("debug", K_DEBUG, 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("log", K_LOG, 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("no", K_NO, UNRESERVED_KEYWORD)
|
||||
PG_KEYWORD("notice", K_NOTICE, 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("query", K_QUERY, UNRESERVED_KEYWORD)
|
||||
PG_KEYWORD("relative", K_RELATIVE, 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("row_count", K_ROW_COUNT, UNRESERVED_KEYWORD)
|
||||
PG_KEYWORD("rowtype", K_ROWTYPE, UNRESERVED_KEYWORD)
|
||||
PG_KEYWORD("scroll", K_SCROLL, UNRESERVED_KEYWORD)
|
||||
PG_KEYWORD("slice", K_SLICE, 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("use_column", K_USE_COLUMN, UNRESERVED_KEYWORD)
|
||||
PG_KEYWORD("use_variable", K_USE_VARIABLE, UNRESERVED_KEYWORD)
|
||||
|
@ -120,13 +120,18 @@ enum
|
||||
};
|
||||
|
||||
/* ----------
|
||||
* GET DIAGNOSTICS system attrs
|
||||
* GET DIAGNOSTICS information items
|
||||
* ----------
|
||||
*/
|
||||
enum
|
||||
{
|
||||
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 */
|
||||
int cmd_type;
|
||||
int lineno;
|
||||
bool is_stacked; /* STACKED or CURRENT diagnostics area? */
|
||||
List *diag_items; /* List of PLpgSQL_diag_item */
|
||||
} 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_getdiag_kindname(int kind);
|
||||
extern void plpgsql_free_function_memory(PLpgSQL_function *func);
|
||||
extern void plpgsql_dumptree(PLpgSQL_function *func);
|
||||
|
||||
|
@ -3607,6 +3607,81 @@ $$ language plpgsql;
|
||||
select raise_test();
|
||||
ERROR: RAISE without parameters cannot be used outside an exception handler
|
||||
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
|
||||
-- SQLSTATE as a keyword, cf bug #5524
|
||||
create or replace function raise_test() returns void as $$
|
||||
|
@ -2941,6 +2941,76 @@ $$ language plpgsql;
|
||||
|
||||
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
|
||||
-- SQLSTATE as a keyword, cf bug #5524
|
||||
create or replace function raise_test() returns void as $$
|
||||
|
Loading…
x
Reference in New Issue
Block a user