mirror of
https://github.com/postgres/postgres.git
synced 2025-07-18 17:42:25 +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.
|
expected.
|
||||||
</para>
|
</para>
|
||||||
</example>
|
</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">
|
<sect3 id="plpgsql-exception-diagnostics">
|
||||||
<title>Obtaining information about an error</title>
|
<title>Obtaining information about an error</title>
|
||||||
@ -2736,6 +2745,54 @@ END;
|
|||||||
</programlisting>
|
</programlisting>
|
||||||
</para>
|
</para>
|
||||||
</sect3>
|
</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>
|
</sect2>
|
||||||
</sect1>
|
</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
|
* Initialization of error output file
|
||||||
*/
|
*/
|
||||||
|
@ -406,6 +406,8 @@ extern void FlushErrorState(void);
|
|||||||
extern void ReThrowError(ErrorData *edata) __attribute__((noreturn));
|
extern void ReThrowError(ErrorData *edata) __attribute__((noreturn));
|
||||||
extern void pg_re_throw(void) __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 */
|
/* Hook for intercepting messages before they are sent to the server log */
|
||||||
typedef void (*emit_log_hook_type) (ErrorData *edata);
|
typedef void (*emit_log_hook_type) (ErrorData *edata);
|
||||||
extern PGDLLIMPORT emit_log_hook_type emit_log_hook;
|
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);
|
estate->cur_error->schema_name);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PLPGSQL_GETDIAG_CONTEXT:
|
||||||
|
{
|
||||||
|
char *contextstackstr = GetErrorContextStack();
|
||||||
|
|
||||||
|
exec_assign_c_string(estate, var, contextstackstr);
|
||||||
|
|
||||||
|
pfree(contextstackstr);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
elog(ERROR, "unrecognized diagnostic item kind: %d",
|
elog(ERROR, "unrecognized diagnostic item kind: %d",
|
||||||
diag_item->kind);
|
diag_item->kind);
|
||||||
|
@ -277,6 +277,8 @@ plpgsql_getdiag_kindname(int kind)
|
|||||||
return "ROW_COUNT";
|
return "ROW_COUNT";
|
||||||
case PLPGSQL_GETDIAG_RESULT_OID:
|
case PLPGSQL_GETDIAG_RESULT_OID:
|
||||||
return "RESULT_OID";
|
return "RESULT_OID";
|
||||||
|
case PLPGSQL_GETDIAG_CONTEXT:
|
||||||
|
return "PG_CONTEXT";
|
||||||
case PLPGSQL_GETDIAG_ERROR_CONTEXT:
|
case PLPGSQL_GETDIAG_ERROR_CONTEXT:
|
||||||
return "PG_EXCEPTION_CONTEXT";
|
return "PG_EXCEPTION_CONTEXT";
|
||||||
case PLPGSQL_GETDIAG_ERROR_DETAIL:
|
case PLPGSQL_GETDIAG_ERROR_DETAIL:
|
||||||
|
@ -303,6 +303,7 @@ static List *read_raise_options(void);
|
|||||||
%token <keyword> K_OPTION
|
%token <keyword> K_OPTION
|
||||||
%token <keyword> K_OR
|
%token <keyword> K_OR
|
||||||
%token <keyword> K_PERFORM
|
%token <keyword> K_PERFORM
|
||||||
|
%token <keyword> K_PG_CONTEXT
|
||||||
%token <keyword> K_PG_DATATYPE_NAME
|
%token <keyword> K_PG_DATATYPE_NAME
|
||||||
%token <keyword> K_PG_EXCEPTION_CONTEXT
|
%token <keyword> K_PG_EXCEPTION_CONTEXT
|
||||||
%token <keyword> K_PG_EXCEPTION_DETAIL
|
%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 */
|
/* these fields are disallowed in stacked case */
|
||||||
case PLPGSQL_GETDIAG_ROW_COUNT:
|
case PLPGSQL_GETDIAG_ROW_COUNT:
|
||||||
case PLPGSQL_GETDIAG_RESULT_OID:
|
case PLPGSQL_GETDIAG_RESULT_OID:
|
||||||
|
case PLPGSQL_GETDIAG_CONTEXT:
|
||||||
if (new->is_stacked)
|
if (new->is_stacked)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
@ -976,6 +978,9 @@ getdiag_item :
|
|||||||
else if (tok_is_keyword(tok, &yylval,
|
else if (tok_is_keyword(tok, &yylval,
|
||||||
K_RESULT_OID, "result_oid"))
|
K_RESULT_OID, "result_oid"))
|
||||||
$$ = PLPGSQL_GETDIAG_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,
|
else if (tok_is_keyword(tok, &yylval,
|
||||||
K_PG_EXCEPTION_DETAIL, "pg_exception_detail"))
|
K_PG_EXCEPTION_DETAIL, "pg_exception_detail"))
|
||||||
$$ = PLPGSQL_GETDIAG_ERROR_DETAIL;
|
$$ = PLPGSQL_GETDIAG_ERROR_DETAIL;
|
||||||
@ -2287,6 +2292,7 @@ unreserved_keyword :
|
|||||||
| K_NO
|
| K_NO
|
||||||
| K_NOTICE
|
| K_NOTICE
|
||||||
| K_OPTION
|
| K_OPTION
|
||||||
|
| K_PG_CONTEXT
|
||||||
| K_PG_DATATYPE_NAME
|
| K_PG_DATATYPE_NAME
|
||||||
| K_PG_EXCEPTION_CONTEXT
|
| K_PG_EXCEPTION_CONTEXT
|
||||||
| K_PG_EXCEPTION_DETAIL
|
| K_PG_EXCEPTION_DETAIL
|
||||||
|
@ -135,6 +135,7 @@ static const ScanKeyword unreserved_keywords[] = {
|
|||||||
PG_KEYWORD("no", K_NO, UNRESERVED_KEYWORD)
|
PG_KEYWORD("no", K_NO, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("notice", K_NOTICE, UNRESERVED_KEYWORD)
|
PG_KEYWORD("notice", K_NOTICE, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("option", K_OPTION, 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_datatype_name", K_PG_DATATYPE_NAME, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("pg_exception_context", K_PG_EXCEPTION_CONTEXT, 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_detail", K_PG_EXCEPTION_DETAIL, UNRESERVED_KEYWORD)
|
||||||
|
@ -124,6 +124,7 @@ enum
|
|||||||
{
|
{
|
||||||
PLPGSQL_GETDIAG_ROW_COUNT,
|
PLPGSQL_GETDIAG_ROW_COUNT,
|
||||||
PLPGSQL_GETDIAG_RESULT_OID,
|
PLPGSQL_GETDIAG_RESULT_OID,
|
||||||
|
PLPGSQL_GETDIAG_CONTEXT,
|
||||||
PLPGSQL_GETDIAG_ERROR_CONTEXT,
|
PLPGSQL_GETDIAG_ERROR_CONTEXT,
|
||||||
PLPGSQL_GETDIAG_ERROR_DETAIL,
|
PLPGSQL_GETDIAG_ERROR_DETAIL,
|
||||||
PLPGSQL_GETDIAG_ERROR_HINT,
|
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
|
CONTEXT: PL/pgSQL function testoa(integer,integer,integer) line 5 at assignment
|
||||||
drop function arrayassign1();
|
drop function arrayassign1();
|
||||||
drop function testoa(x1 int, x2 int, x3 int);
|
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 arrayassign1();
|
||||||
drop function testoa(x1 int, x2 int, x3 int);
|
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