From 3d4890c0c5d27dfdf7d1a8816d7bdcdba3c39d21 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 18 Jul 2011 14:46:27 -0400 Subject: [PATCH] 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 --- doc/src/sgml/plpgsql.sgml | 121 ++++++++++++++++++++++---- src/backend/utils/errcodes.txt | 55 +++++++----- src/pl/plpgsql/src/gram.y | 89 ++++++++++++++++++- src/pl/plpgsql/src/pl_exec.c | 71 +++++++++++++-- src/pl/plpgsql/src/pl_funcs.c | 47 ++++++---- src/pl/plpgsql/src/pl_scanner.c | 7 ++ src/pl/plpgsql/src/plpgsql.h | 11 ++- src/test/regress/expected/plpgsql.out | 75 ++++++++++++++++ src/test/regress/sql/plpgsql.sql | 70 +++++++++++++++ 9 files changed, 480 insertions(+), 66 deletions(-) diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index 20bc991083..08c3658d5e 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 <command>UPDATE</>/<command>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 d27fe2c3d9..c7d5b2a638 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 4e2b7058f0..92b54dd9cf 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 b7ffb717f9..de1aece92a 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 1f83114d9b..030ed07ac5 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 e1c0b62595..76e8436e50 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 89103aea8c..c543f1cf9d 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 bfabcbc8b4..bed34c8a87 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 14fb4578c6..05f031575c 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 $$ -- GitLab