mirror of
https://github.com/postgres/postgres.git
synced 2025-07-17 06:41:09 +03:00
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.
This commit is contained in:
@ -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);
|
||||
|
Reference in New Issue
Block a user