]> granicus.if.org Git - postgresql/commitdiff
Add GET STACKED DIAGNOSTICS plpgsql command to retrieve exception info.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 18 Jul 2011 18:46:27 +0000 (14:46 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 18 Jul 2011 18:47:18 +0000 (14:47 -0400)
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
src/backend/utils/errcodes.txt
src/pl/plpgsql/src/gram.y
src/pl/plpgsql/src/pl_exec.c
src/pl/plpgsql/src/pl_funcs.c
src/pl/plpgsql/src/pl_scanner.c
src/pl/plpgsql/src/plpgsql.h
src/test/regress/expected/plpgsql.out
src/test/regress/sql/plpgsql.sql

index 20bc99108362486bc5a1d48ef7878dda6538a200..08c3658d5e37171f779e772089b448a76fbec923 100644 (file)
@@ -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>
 
index d27fe2c3d902ab505e553cc0093ffd51ecc41420..c7d5b2a638df9bf249ac5719959b476f0513dd4c 100644 (file)
@@ -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
 
index 4e2b7058f0c471182b8276a101e33fa96ed01bdb..92b54dd9cf629dc091059d717c869111f52b0b01 100644 (file)
@@ -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
index b7ffb717f9a412d02625b17e4fc8bf0b33077f6c..de1aece92a16532dd3a059723ce7259844c3733f 100644 (file)
@@ -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
  *
index 1f83114d9b15cae9260fd30fb9ced487db8b9246..030ed07ac55f999af6160bbac9294a4a2f360a42 100644 (file)
@@ -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");
 }
index e1c0b625954a6c6cee45932e849df681e8d1508b..76e8436e50e9c1d4919a60916b0cabfa83a13eb1 100644 (file)
@@ -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)
index 89103aea8c5d3fa967ff2ab4a23105039dfe3a8d..c543f1cf9dd43790a64741870a6291baaf6853e2 100644 (file)
@@ -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);
 
index bfabcbc8b448379cffec1a0f3fb8568d8a0469a8..bed34c8a87ca97fa0be61c4bfc4021285f02a4cd 100644 (file)
@@ -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 $$
index 14fb4578c609b13cca464659bc456e34239557a3..05f031575c02490b5afa048c405b6fc51b382bf8 100644 (file)
@@ -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 $$