Commit 83128325 authored by Stephen Frost's avatar Stephen Frost

Add GET DIAGNOSTICS ... PG_CONTEXT in PL/PgSQL

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.
parent fa2fad3c
......@@ -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>
......
......@@ -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
*/
......
......@@ -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;
......
......@@ -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);
......
......@@ -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:
......
......@@ -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
......
......@@ -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)
......
......@@ -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,
......
......@@ -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);
......@@ -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);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment