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>
</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>
}
+/*
+ * 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
*/
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;
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);
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:
%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
/* 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),
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;
| K_NO
| K_NOTICE
| K_OPTION
+ | K_PG_CONTEXT
| K_PG_DATATYPE_NAME
| K_PG_EXCEPTION_CONTEXT
| K_PG_EXCEPTION_DETAIL
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)
{
PLPGSQL_GETDIAG_ROW_COUNT,
PLPGSQL_GETDIAG_RESULT_OID,
+ PLPGSQL_GETDIAG_CONTEXT,
PLPGSQL_GETDIAG_ERROR_CONTEXT,
PLPGSQL_GETDIAG_ERROR_DETAIL,
PLPGSQL_GETDIAG_ERROR_HINT,
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);
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);