]> granicus.if.org Git - postgresql/commitdiff
Add GET DIAGNOSTICS ... PG_CONTEXT in PL/PgSQL
authorStephen Frost <sfrost@snowman.net>
Wed, 24 Jul 2013 22:53:27 +0000 (18:53 -0400)
committerStephen Frost <sfrost@snowman.net>
Wed, 24 Jul 2013 22:53:27 +0000 (18:53 -0400)
This adds the ability to get the call stack as a string from within a
PL/PgSQL function, which can be handy for logging to a table, or to
include in a useful message to an end-user.

Pavel Stehule, reviewed by Rushabh Lathia and rather heavily whacked
around by Stephen Frost.

doc/src/sgml/plpgsql.sgml
src/backend/utils/error/elog.c
src/include/utils/elog.h
src/pl/plpgsql/src/pl_exec.c
src/pl/plpgsql/src/pl_funcs.c
src/pl/plpgsql/src/pl_gram.y
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 6fffec18b7081081a0bea07ad836162a2b5e9206..0fe6bcf470d86e5f66fcb346e05e914006872b4d 100644 (file)
@@ -2613,6 +2613,15 @@ SELECT merge_db(1, 'dennis');
      expected.
     </para>
     </example>
+  </sect2>
+
+   <sect2 id="plpgsql-diagnostics">
+    <title>Getting Diagnostics Information</title>
+
+    <indexterm>
+     <primary>diagnostics</primary>
+     <secondary>in PL/pgSQL</secondary>
+    </indexterm>
 
    <sect3 id="plpgsql-exception-diagnostics">
     <title>Obtaining information about an error</title>
@@ -2736,6 +2745,54 @@ END;
 </programlisting>
     </para>
    </sect3>
+
+  <sect3 id="plpgsql-get-diagnostics-context">
+   <title>Obtaining the call stack context information</title>
+
+   <para>
+
+<synopsis>
+GET <optional> CURRENT </optional> DIAGNOSTICS <replaceable>variable</replaceable> = <replaceable>PG_CONTEXT</replaceable> <optional> , ... </optional>;
+</synopsis>
+
+
+    Calling <command>GET DIAGNOSTICS</command> with status
+    item <varname>PG_CONTEXT</> will return a text string with line(s) of
+    text describing the call stack.  The first row refers to the
+    current function and currently executing <command>GET DIAGNOSTICS</command>
+    command.  The second and any subsequent rows refer to the calling functions
+    up the call stack.
+
+<programlisting>
+CREATE OR REPLACE FUNCTION public.outer_func() RETURNS integer AS $$
+BEGIN
+  RETURN inner_func();
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION public.inner_func() RETURNS integer AS  $$
+DECLARE
+  stack text;
+BEGIN
+  GET DIAGNOSTICS stack = PG_CONTEXT;
+  RAISE NOTICE e'--- Call Stack ---\n%', stack;
+  RETURN 1;
+END;
+$$ LANGUAGE plpgsql;
+
+SELECT outer_func();
+
+NOTICE:  --- Call Stack ---
+PL/pgSQL function inner_func() line 4 at GET DIAGNOSTICS
+PL/pgSQL function outer_func() line 3 at RETURN
+ outer_func
+ ------------
+           1
+(1 row)
+</programlisting>
+
+   </para>
+  </sect3>
   </sect2>
   </sect1>
 
index 706c01eca55f37e6c8f0370e5162f00b512fb2a4..5090ba5d1bba6491b826645d9d96ceb596fe6d5f 100644 (file)
@@ -1626,6 +1626,75 @@ pg_re_throw(void)
 }
 
 
+/*
+ * GetErrorContextStack - Return the error context stack
+ *
+ * Returns a pstrdup'd string in the caller's context which includes the full
+ * call stack.  It is the caller's responsibility to ensure this string is
+ * pfree'd (or its context cleaned up) when done.
+ *
+ * This information is collected by traversing the error contexts and calling
+ * each context's callback function, each of which is expected to call
+ * errcontext() to return a string which can be presented to the user.
+ */
+char *
+GetErrorContextStack(void)
+{
+       char                               *result = NULL;
+       ErrorData                          *edata;
+       ErrorContextCallback   *econtext;
+       MemoryContext                   oldcontext = CurrentMemoryContext;
+
+       /* this function should not be called from an exception handler */
+       Assert(recursion_depth == 0);
+
+       /* Check that we have enough room on the stack for ourselves */
+       if (++errordata_stack_depth >= ERRORDATA_STACK_SIZE)
+       {
+               /*
+                * Stack not big enough..       Something bad has happened, therefore
+                * PANIC as we may be in an infinite loop.
+                */
+               errordata_stack_depth = -1;             /* make room on stack */
+               ereport(PANIC, (errmsg_internal("ERRORDATA_STACK_SIZE exceeded")));
+       }
+
+       /* Initialize data for this error frame */
+       edata = &errordata[errordata_stack_depth];
+       MemSet(edata, 0, sizeof(ErrorData));
+
+       /* Use ErrorContext as a short lived context for the callbacks */
+       MemoryContextSwitchTo(ErrorContext);
+
+       /*
+        * Call any context callback functions to collect the context information
+        * into edata->context.
+        *
+        * Errors occurring in callback functions should go through the regular
+        * error handling code which should handle any recursive errors.
+        */
+       for (econtext = error_context_stack;
+                econtext != NULL;
+                econtext = econtext->previous)
+               (*econtext->callback) (econtext->arg);
+
+       MemoryContextSwitchTo(oldcontext);
+
+       /*
+        * Copy out the string into the caller's context, so we can free our
+        * error context and reset the error stack.  Caller is expected to
+        * pfree() the result or throw away the context.
+        */
+       if (edata->context)
+               result = pstrdup(edata->context);
+
+       /* Reset error stack */
+       FlushErrorState();
+
+       return result;
+}
+
+
 /*
  * Initialization of error output file
  */
index 85bd2fdf8a2dbcde7d0a70f3507b8ae1fc917ac5..add08729ced46d546c4454795751829d0ebf2cbd 100644 (file)
@@ -406,6 +406,8 @@ extern void FlushErrorState(void);
 extern void ReThrowError(ErrorData *edata) __attribute__((noreturn));
 extern void pg_re_throw(void) __attribute__((noreturn));
 
+extern char *GetErrorContextStack(void);
+
 /* Hook for intercepting messages before they are sent to the server log */
 typedef void (*emit_log_hook_type) (ErrorData *edata);
 extern PGDLLIMPORT emit_log_hook_type emit_log_hook;
index 5b142e3bee6ccf929f3d4ec8ef3bf6c46467dc90..f74326a53c5e43b26bb3580ec84ec2968e941907 100644 (file)
@@ -1599,6 +1599,16 @@ exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt)
                                                                         estate->cur_error->schema_name);
                                break;
 
+                       case PLPGSQL_GETDIAG_CONTEXT:
+                               {
+                                       char *contextstackstr = GetErrorContextStack();
+
+                                       exec_assign_c_string(estate, var, contextstackstr);
+
+                                       pfree(contextstackstr);
+                               }
+                               break;
+
                        default:
                                elog(ERROR, "unrecognized diagnostic item kind: %d",
                                         diag_item->kind);
index 87e528fe5bfdd4e820f581dfb72ec43905b24ebd..d9f1dc8c64c0bfdf314456a1d25c04da2a2878ca 100644 (file)
@@ -277,6 +277,8 @@ plpgsql_getdiag_kindname(int kind)
                        return "ROW_COUNT";
                case PLPGSQL_GETDIAG_RESULT_OID:
                        return "RESULT_OID";
+               case PLPGSQL_GETDIAG_CONTEXT:
+                       return "PG_CONTEXT";
                case PLPGSQL_GETDIAG_ERROR_CONTEXT:
                        return "PG_EXCEPTION_CONTEXT";
                case PLPGSQL_GETDIAG_ERROR_DETAIL:
index 086987a58a41e802b4322bcb02efd5b312d6d9a6..263abeff44b2620ecb9db5e8dbd489ac45ee38f9 100644 (file)
@@ -303,6 +303,7 @@ static      List                    *read_raise_options(void);
 %token <keyword>       K_OPTION
 %token <keyword>       K_OR
 %token <keyword>       K_PERFORM
+%token <keyword>       K_PG_CONTEXT
 %token <keyword>       K_PG_DATATYPE_NAME
 %token <keyword>       K_PG_EXCEPTION_CONTEXT
 %token <keyword>       K_PG_EXCEPTION_DETAIL
@@ -894,6 +895,7 @@ stmt_getdiag        : K_GET getdiag_area_opt K_DIAGNOSTICS getdiag_list ';'
                                                                /* these fields are disallowed in stacked case */
                                                                case PLPGSQL_GETDIAG_ROW_COUNT:
                                                                case PLPGSQL_GETDIAG_RESULT_OID:
+                                                               case PLPGSQL_GETDIAG_CONTEXT:
                                                                        if (new->is_stacked)
                                                                                ereport(ERROR,
                                                                                                (errcode(ERRCODE_SYNTAX_ERROR),
@@ -976,6 +978,9 @@ 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_CONTEXT, "pg_context"))
+                                                       $$ = PLPGSQL_GETDIAG_CONTEXT;
                                                else if (tok_is_keyword(tok, &yylval,
                                                                                                K_PG_EXCEPTION_DETAIL, "pg_exception_detail"))
                                                        $$ = PLPGSQL_GETDIAG_ERROR_DETAIL;
@@ -2287,6 +2292,7 @@ unreserved_keyword        :
                                | K_NO
                                | K_NOTICE
                                | K_OPTION
+                               | K_PG_CONTEXT
                                | K_PG_DATATYPE_NAME
                                | K_PG_EXCEPTION_CONTEXT
                                | K_PG_EXCEPTION_DETAIL
index 84c51260d2554a80de77f92074462cc4a415a308..35771c2595e0690ca5d1bf328a861ff70eb06dd4 100644 (file)
@@ -135,6 +135,7 @@ static const ScanKeyword unreserved_keywords[] = {
        PG_KEYWORD("no", K_NO, UNRESERVED_KEYWORD)
        PG_KEYWORD("notice", K_NOTICE, UNRESERVED_KEYWORD)
        PG_KEYWORD("option", K_OPTION, UNRESERVED_KEYWORD)
+       PG_KEYWORD("pg_context", K_PG_CONTEXT, UNRESERVED_KEYWORD)
        PG_KEYWORD("pg_datatype_name", K_PG_DATATYPE_NAME, UNRESERVED_KEYWORD)
        PG_KEYWORD("pg_exception_context", K_PG_EXCEPTION_CONTEXT, UNRESERVED_KEYWORD)
        PG_KEYWORD("pg_exception_detail", K_PG_EXCEPTION_DETAIL, UNRESERVED_KEYWORD)
index cdf39929e0f85baa48bbad0e88627b63c6f069d6..d49e0b002176d11d4e093b1970e469ea6d823f97 100644 (file)
@@ -124,6 +124,7 @@ enum
 {
        PLPGSQL_GETDIAG_ROW_COUNT,
        PLPGSQL_GETDIAG_RESULT_OID,
+       PLPGSQL_GETDIAG_CONTEXT,
        PLPGSQL_GETDIAG_ERROR_CONTEXT,
        PLPGSQL_GETDIAG_ERROR_DETAIL,
        PLPGSQL_GETDIAG_ERROR_HINT,
index 7feea0ac476afc452ee57a9c7446a2873df49187..4394a3a1a7a2551f72ca3f6969451958146e266c 100644 (file)
@@ -4897,3 +4897,51 @@ ERROR:  value for domain orderedarray violates check constraint "sorted"
 CONTEXT:  PL/pgSQL function testoa(integer,integer,integer) line 5 at assignment
 drop function arrayassign1();
 drop function testoa(x1 int, x2 int, x3 int);
+-- access to call stack
+create function inner_func(int)
+returns int as $$
+declare _context text;
+begin
+  get diagnostics _context = pg_context;
+  raise notice '***%***', _context;
+  return 2 * $1;
+end;
+$$ language plpgsql;
+create or replace function outer_func(int)
+returns int as $$
+begin
+  return inner_func($1);
+end;
+$$ language plpgsql;
+create or replace function outer_outer_func(int)
+returns int as $$
+begin
+  return outer_func($1);
+end;
+$$ language plpgsql;
+select outer_outer_func(10);
+NOTICE:  ***PL/pgSQL function inner_func(integer) line 4 at GET DIAGNOSTICS
+PL/pgSQL function outer_func(integer) line 3 at RETURN
+PL/pgSQL function outer_outer_func(integer) line 3 at RETURN***
+CONTEXT:  PL/pgSQL function outer_func(integer) line 3 at RETURN
+PL/pgSQL function outer_outer_func(integer) line 3 at RETURN
+ outer_outer_func 
+------------------
+               20
+(1 row)
+
+-- repeated call should to work
+select outer_outer_func(20);
+NOTICE:  ***PL/pgSQL function inner_func(integer) line 4 at GET DIAGNOSTICS
+PL/pgSQL function outer_func(integer) line 3 at RETURN
+PL/pgSQL function outer_outer_func(integer) line 3 at RETURN***
+CONTEXT:  PL/pgSQL function outer_func(integer) line 3 at RETURN
+PL/pgSQL function outer_outer_func(integer) line 3 at RETURN
+ outer_outer_func 
+------------------
+               40
+(1 row)
+
+drop function outer_outer_func(int);
+drop function outer_func(int);
+drop function inner_func(int);
index 0f8465faa470d28c1764cd5625f5db814d5c46e6..b59715267e61eca9b2dd8571c8487178e7a948c1 100644 (file)
@@ -3880,3 +3880,36 @@ select testoa(1,2,1); -- fail at update
 
 drop function arrayassign1();
 drop function testoa(x1 int, x2 int, x3 int);
+
+-- access to call stack
+create function inner_func(int)
+returns int as $$
+declare _context text;
+begin
+  get diagnostics _context = pg_context;
+  raise notice '***%***', _context;
+  return 2 * $1;
+end;
+$$ language plpgsql;
+
+create or replace function outer_func(int)
+returns int as $$
+begin
+  return inner_func($1);
+end;
+$$ language plpgsql;
+
+create or replace function outer_outer_func(int)
+returns int as $$
+begin
+  return outer_func($1);
+end;
+$$ language plpgsql;
+
+select outer_outer_func(10);
+-- repeated call should to work
+select outer_outer_func(20);
+
+drop function outer_outer_func(int);
+drop function outer_func(int);
+drop function inner_func(int);