diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 20bc9910836..08c3658d5e3 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -1387,11 +1387,11 @@ EXECUTE format('UPDATE tbl SET %I = $1 WHERE key = $2', colname)
command, which has the form:
-GET DIAGNOSTICS variable = item , ... ;
+GET CURRENT DIAGNOSTICS variable = item , ... ;
This command allows retrieval of system status indicators. Each
- item is a key word identifying a state
+ item 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 ROW_COUNT>, the number of rows
@@ -2522,16 +2522,6 @@ END;
-
- Within an exception handler, the SQLSTATE
- variable contains the error code that corresponds to the
- exception that was raised (refer to for a list of possible error
- codes). The SQLERRM variable contains the
- error message associated with the exception. These variables are
- undefined outside exception handlers.
-
-
Exceptions with UPDATE>/INSERT>
@@ -2568,11 +2558,112 @@ LANGUAGE plpgsql;
SELECT merge_db(1, 'david');
SELECT merge_db(1, 'dennis');
- This example assumes the unique_violation> error is caused by
- the INSERT>, and not by an INSERT> trigger function
- on the table.
+
+ This coding assumes the unique_violation> error is caused by
+ the INSERT>, and not by, say, an 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.
+
+
+ Obtaining information about an error
+
+
+ Exception handlers frequently need to identify the specific error that
+ occurred. There are two ways to get information about the current
+ exception in PL/pgSQL>: special variables and the
+ GET STACKED DIAGNOSTICS command.
+
+
+
+ Within an exception handler, the special variable
+ SQLSTATE contains the error code that corresponds to
+ the exception that was raised (refer to
+ for a list of possible error codes). The special variable
+ SQLERRM contains the error message associated with the
+ exception. These variables are undefined outside exception handlers.
+
+
+
+ Within an exception handler, one may also retrieve
+ information about the current exception by using the
+ GET STACKED DIAGNOSTICS command, which has the form:
+
+
+GET STACKED DIAGNOSTICS variable = item , ... ;
+
+
+ Each item 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:
+
+
+ Error diagnostics values
+
+
+
+ Name
+ Type
+ Description
+
+
+
+
+ RETURNED_SQLSTATE
+ text
+ the SQLSTATE error code of the exception
+
+
+ MESSAGE_TEXT
+ text
+ the text of the exception's primary message
+
+
+ PG_EXCEPTION_DETAIL
+ text
+ the text of the exception's detail message, if any
+
+
+ PG_EXCEPTION_HINT
+ text
+ the text of the exception's hint message, if any
+
+
+ PG_EXCEPTION_CONTEXT
+ text
+ line(s) of text describing the call stack
+
+
+
+
+
+
+
+ If the exception did not set a value for an item, an empty string
+ will be returned.
+
+
+
+ Here is an example:
+
+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;
+
+
+
diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index d27fe2c3d90..c7d5b2a638d 100644
--- a/src/backend/utils/errcodes.txt
+++ b/src/backend/utils/errcodes.txt
@@ -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
diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y
index 4e2b7058f0c..92b54dd9cf6 100644
--- a/src/pl/plpgsql/src/gram.y
+++ b/src/pl/plpgsql/src/gram.y
@@ -203,6 +203,7 @@ static List *read_raise_options(void);
%type case_when
%type case_when_list opt_case_else
+%type getdiag_area_opt
%type getdiag_list
%type getdiag_list_item
%type getdiag_item getdiag_target
@@ -251,6 +252,7 @@ static List *read_raise_options(void);
%token K_COLLATE
%token K_CONSTANT
%token K_CONTINUE
+%token K_CURRENT
%token K_CURSOR
%token K_DEBUG
%token K_DECLARE
@@ -284,6 +286,7 @@ static List *read_raise_options(void);
%token K_LOG
%token K_LOOP
%token K_MESSAGE
+%token K_MESSAGE_TEXT
%token K_MOVE
%token K_NEXT
%token K_NO
@@ -294,18 +297,23 @@ static List *read_raise_options(void);
%token K_OPTION
%token K_OR
%token K_PERFORM
+%token K_PG_EXCEPTION_CONTEXT
+%token K_PG_EXCEPTION_DETAIL
+%token K_PG_EXCEPTION_HINT
%token K_PRIOR
%token K_QUERY
%token K_RAISE
%token K_RELATIVE
%token K_RESULT_OID
%token K_RETURN
+%token K_RETURNED_SQLSTATE
%token K_REVERSE
%token K_ROWTYPE
%token K_ROW_COUNT
%token K_SCROLL
%token K_SLICE
%token K_SQLSTATE
+%token K_STACKED
%token K_STRICT
%token K_THEN
%token 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
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b7ffb717f9a..de1aece92a1 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -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
*
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index 1f83114d9b1..030ed07ac55 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -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");
}
diff --git a/src/pl/plpgsql/src/pl_scanner.c b/src/pl/plpgsql/src/pl_scanner.c
index e1c0b625954..76e8436e50e 100644
--- a/src/pl/plpgsql/src/pl_scanner.c
+++ b/src/pl/plpgsql/src/pl_scanner.c
@@ -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)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 89103aea8c5..c543f1cf9dd 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -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);
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index bfabcbc8b44..bed34c8a87c 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -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 $$
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 14fb4578c60..05f031575c0 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -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 $$