Skip to content

Commit 8312832

Browse files
committed
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.
1 parent fa2fad3 commit 8312832

File tree

10 files changed

+229
-0
lines changed

10 files changed

+229
-0
lines changed

doc/src/sgml/plpgsql.sgml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2613,6 +2613,15 @@ SELECT merge_db(1, 'dennis');
26132613
expected.
26142614
</para>
26152615
</example>
2616+
</sect2>
2617+
2618+
<sect2 id="plpgsql-diagnostics">
2619+
<title>Getting Diagnostics Information</title>
2620+
2621+
<indexterm>
2622+
<primary>diagnostics</primary>
2623+
<secondary>in PL/pgSQL</secondary>
2624+
</indexterm>
26162625

26172626
<sect3 id="plpgsql-exception-diagnostics">
26182627
<title>Obtaining information about an error</title>
@@ -2736,6 +2745,54 @@ END;
27362745
</programlisting>
27372746
</para>
27382747
</sect3>
2748+
2749+
<sect3 id="plpgsql-get-diagnostics-context">
2750+
<title>Obtaining the call stack context information</title>
2751+
2752+
<para>
2753+
2754+
<synopsis>
2755+
GET <optional> CURRENT </optional> DIAGNOSTICS <replaceable>variable</replaceable> = <replaceable>PG_CONTEXT</replaceable> <optional> , ... </optional>;
2756+
</synopsis>
2757+
2758+
2759+
Calling <command>GET DIAGNOSTICS</command> with status
2760+
item <varname>PG_CONTEXT</> will return a text string with line(s) of
2761+
text describing the call stack. The first row refers to the
2762+
current function and currently executing <command>GET DIAGNOSTICS</command>
2763+
command. The second and any subsequent rows refer to the calling functions
2764+
up the call stack.
2765+
2766+
<programlisting>
2767+
CREATE OR REPLACE FUNCTION public.outer_func() RETURNS integer AS $$
2768+
BEGIN
2769+
RETURN inner_func();
2770+
END;
2771+
$$ LANGUAGE plpgsql;
2772+
2773+
CREATE OR REPLACE FUNCTION public.inner_func() RETURNS integer AS $$
2774+
DECLARE
2775+
stack text;
2776+
BEGIN
2777+
GET DIAGNOSTICS stack = PG_CONTEXT;
2778+
RAISE NOTICE e'--- Call Stack ---\n%', stack;
2779+
RETURN 1;
2780+
END;
2781+
$$ LANGUAGE plpgsql;
2782+
2783+
SELECT outer_func();
2784+
2785+
NOTICE: --- Call Stack ---
2786+
PL/pgSQL function inner_func() line 4 at GET DIAGNOSTICS
2787+
PL/pgSQL function outer_func() line 3 at RETURN
2788+
outer_func
2789+
------------
2790+
1
2791+
(1 row)
2792+
</programlisting>
2793+
2794+
</para>
2795+
</sect3>
27392796
</sect2>
27402797
</sect1>
27412798

src/backend/utils/error/elog.c

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1626,6 +1626,75 @@ pg_re_throw(void)
16261626
}
16271627

16281628

1629+
/*
1630+
* GetErrorContextStack - Return the error context stack
1631+
*
1632+
* Returns a pstrdup'd string in the caller's context which includes the full
1633+
* call stack. It is the caller's responsibility to ensure this string is
1634+
* pfree'd (or its context cleaned up) when done.
1635+
*
1636+
* This information is collected by traversing the error contexts and calling
1637+
* each context's callback function, each of which is expected to call
1638+
* errcontext() to return a string which can be presented to the user.
1639+
*/
1640+
char *
1641+
GetErrorContextStack(void)
1642+
{
1643+
char *result = NULL;
1644+
ErrorData *edata;
1645+
ErrorContextCallback *econtext;
1646+
MemoryContext oldcontext = CurrentMemoryContext;
1647+
1648+
/* this function should not be called from an exception handler */
1649+
Assert(recursion_depth == 0);
1650+
1651+
/* Check that we have enough room on the stack for ourselves */
1652+
if (++errordata_stack_depth >= ERRORDATA_STACK_SIZE)
1653+
{
1654+
/*
1655+
* Stack not big enough.. Something bad has happened, therefore
1656+
* PANIC as we may be in an infinite loop.
1657+
*/
1658+
errordata_stack_depth = -1; /* make room on stack */
1659+
ereport(PANIC, (errmsg_internal("ERRORDATA_STACK_SIZE exceeded")));
1660+
}
1661+
1662+
/* Initialize data for this error frame */
1663+
edata = &errordata[errordata_stack_depth];
1664+
MemSet(edata, 0, sizeof(ErrorData));
1665+
1666+
/* Use ErrorContext as a short lived context for the callbacks */
1667+
MemoryContextSwitchTo(ErrorContext);
1668+
1669+
/*
1670+
* Call any context callback functions to collect the context information
1671+
* into edata->context.
1672+
*
1673+
* Errors occurring in callback functions should go through the regular
1674+
* error handling code which should handle any recursive errors.
1675+
*/
1676+
for (econtext = error_context_stack;
1677+
econtext != NULL;
1678+
econtext = econtext->previous)
1679+
(*econtext->callback) (econtext->arg);
1680+
1681+
MemoryContextSwitchTo(oldcontext);
1682+
1683+
/*
1684+
* Copy out the string into the caller's context, so we can free our
1685+
* error context and reset the error stack. Caller is expected to
1686+
* pfree() the result or throw away the context.
1687+
*/
1688+
if (edata->context)
1689+
result = pstrdup(edata->context);
1690+
1691+
/* Reset error stack */
1692+
FlushErrorState();
1693+
1694+
return result;
1695+
}
1696+
1697+
16291698
/*
16301699
* Initialization of error output file
16311700
*/

src/include/utils/elog.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,8 @@ extern void FlushErrorState(void);
406406
extern void ReThrowError(ErrorData *edata) __attribute__((noreturn));
407407
extern void pg_re_throw(void) __attribute__((noreturn));
408408

409+
extern char *GetErrorContextStack(void);
410+
409411
/* Hook for intercepting messages before they are sent to the server log */
410412
typedef void (*emit_log_hook_type) (ErrorData *edata);
411413
extern PGDLLIMPORT emit_log_hook_type emit_log_hook;

src/pl/plpgsql/src/pl_exec.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1599,6 +1599,16 @@ exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt)
15991599
estate->cur_error->schema_name);
16001600
break;
16011601

1602+
case PLPGSQL_GETDIAG_CONTEXT:
1603+
{
1604+
char *contextstackstr = GetErrorContextStack();
1605+
1606+
exec_assign_c_string(estate, var, contextstackstr);
1607+
1608+
pfree(contextstackstr);
1609+
}
1610+
break;
1611+
16021612
default:
16031613
elog(ERROR, "unrecognized diagnostic item kind: %d",
16041614
diag_item->kind);

src/pl/plpgsql/src/pl_funcs.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,8 @@ plpgsql_getdiag_kindname(int kind)
277277
return "ROW_COUNT";
278278
case PLPGSQL_GETDIAG_RESULT_OID:
279279
return "RESULT_OID";
280+
case PLPGSQL_GETDIAG_CONTEXT:
281+
return "PG_CONTEXT";
280282
case PLPGSQL_GETDIAG_ERROR_CONTEXT:
281283
return "PG_EXCEPTION_CONTEXT";
282284
case PLPGSQL_GETDIAG_ERROR_DETAIL:

src/pl/plpgsql/src/pl_gram.y

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ static List *read_raise_options(void);
303303
%token <keyword> K_OPTION
304304
%token <keyword> K_OR
305305
%token <keyword> K_PERFORM
306+
%token <keyword> K_PG_CONTEXT
306307
%token <keyword> K_PG_DATATYPE_NAME
307308
%token <keyword> K_PG_EXCEPTION_CONTEXT
308309
%token <keyword> K_PG_EXCEPTION_DETAIL
@@ -894,6 +895,7 @@ stmt_getdiag : K_GET getdiag_area_opt K_DIAGNOSTICS getdiag_list ';'
894895
/* these fields are disallowed in stacked case */
895896
case PLPGSQL_GETDIAG_ROW_COUNT:
896897
case PLPGSQL_GETDIAG_RESULT_OID:
898+
case PLPGSQL_GETDIAG_CONTEXT:
897899
if (new->is_stacked)
898900
ereport(ERROR,
899901
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -976,6 +978,9 @@ getdiag_item :
976978
else if (tok_is_keyword(tok, &yylval,
977979
K_RESULT_OID, "result_oid"))
978980
$$ = PLPGSQL_GETDIAG_RESULT_OID;
981+
else if (tok_is_keyword(tok, &yylval,
982+
K_PG_CONTEXT, "pg_context"))
983+
$$ = PLPGSQL_GETDIAG_CONTEXT;
979984
else if (tok_is_keyword(tok, &yylval,
980985
K_PG_EXCEPTION_DETAIL, "pg_exception_detail"))
981986
$$ = PLPGSQL_GETDIAG_ERROR_DETAIL;
@@ -2287,6 +2292,7 @@ unreserved_keyword :
22872292
| K_NO
22882293
| K_NOTICE
22892294
| K_OPTION
2295+
| K_PG_CONTEXT
22902296
| K_PG_DATATYPE_NAME
22912297
| K_PG_EXCEPTION_CONTEXT
22922298
| K_PG_EXCEPTION_DETAIL

src/pl/plpgsql/src/pl_scanner.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ static const ScanKeyword unreserved_keywords[] = {
135135
PG_KEYWORD("no", K_NO, UNRESERVED_KEYWORD)
136136
PG_KEYWORD("notice", K_NOTICE, UNRESERVED_KEYWORD)
137137
PG_KEYWORD("option", K_OPTION, UNRESERVED_KEYWORD)
138+
PG_KEYWORD("pg_context", K_PG_CONTEXT, UNRESERVED_KEYWORD)
138139
PG_KEYWORD("pg_datatype_name", K_PG_DATATYPE_NAME, UNRESERVED_KEYWORD)
139140
PG_KEYWORD("pg_exception_context", K_PG_EXCEPTION_CONTEXT, UNRESERVED_KEYWORD)
140141
PG_KEYWORD("pg_exception_detail", K_PG_EXCEPTION_DETAIL, UNRESERVED_KEYWORD)

src/pl/plpgsql/src/plpgsql.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ enum
124124
{
125125
PLPGSQL_GETDIAG_ROW_COUNT,
126126
PLPGSQL_GETDIAG_RESULT_OID,
127+
PLPGSQL_GETDIAG_CONTEXT,
127128
PLPGSQL_GETDIAG_ERROR_CONTEXT,
128129
PLPGSQL_GETDIAG_ERROR_DETAIL,
129130
PLPGSQL_GETDIAG_ERROR_HINT,

src/test/regress/expected/plpgsql.out

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4897,3 +4897,51 @@ ERROR: value for domain orderedarray violates check constraint "sorted"
48974897
CONTEXT: PL/pgSQL function testoa(integer,integer,integer) line 5 at assignment
48984898
drop function arrayassign1();
48994899
drop function testoa(x1 int, x2 int, x3 int);
4900+
-- access to call stack
4901+
create function inner_func(int)
4902+
returns int as $$
4903+
declare _context text;
4904+
begin
4905+
get diagnostics _context = pg_context;
4906+
raise notice '***%***', _context;
4907+
return 2 * $1;
4908+
end;
4909+
$$ language plpgsql;
4910+
create or replace function outer_func(int)
4911+
returns int as $$
4912+
begin
4913+
return inner_func($1);
4914+
end;
4915+
$$ language plpgsql;
4916+
create or replace function outer_outer_func(int)
4917+
returns int as $$
4918+
begin
4919+
return outer_func($1);
4920+
end;
4921+
$$ language plpgsql;
4922+
select outer_outer_func(10);
4923+
NOTICE: ***PL/pgSQL function inner_func(integer) line 4 at GET DIAGNOSTICS
4924+
PL/pgSQL function outer_func(integer) line 3 at RETURN
4925+
PL/pgSQL function outer_outer_func(integer) line 3 at RETURN***
4926+
CONTEXT: PL/pgSQL function outer_func(integer) line 3 at RETURN
4927+
PL/pgSQL function outer_outer_func(integer) line 3 at RETURN
4928+
outer_outer_func
4929+
------------------
4930+
20
4931+
(1 row)
4932+
4933+
-- repeated call should to work
4934+
select outer_outer_func(20);
4935+
NOTICE: ***PL/pgSQL function inner_func(integer) line 4 at GET DIAGNOSTICS
4936+
PL/pgSQL function outer_func(integer) line 3 at RETURN
4937+
PL/pgSQL function outer_outer_func(integer) line 3 at RETURN***
4938+
CONTEXT: PL/pgSQL function outer_func(integer) line 3 at RETURN
4939+
PL/pgSQL function outer_outer_func(integer) line 3 at RETURN
4940+
outer_outer_func
4941+
------------------
4942+
40
4943+
(1 row)
4944+
4945+
drop function outer_outer_func(int);
4946+
drop function outer_func(int);
4947+
drop function inner_func(int);

src/test/regress/sql/plpgsql.sql

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3880,3 +3880,36 @@ select testoa(1,2,1); -- fail at update
38803880
38813881
drop function arrayassign1();
38823882
drop function testoa(x1 int, x2 int, x3 int);
3883+
3884+
-- access to call stack
3885+
create function inner_func(int)
3886+
returns int as $$
3887+
declare _context text;
3888+
begin
3889+
get diagnostics _context = pg_context;
3890+
raise notice '***%***', _context;
3891+
return 2 * $1;
3892+
end;
3893+
$$ language plpgsql;
3894+
3895+
create or replace function outer_func(int)
3896+
returns int as $$
3897+
begin
3898+
return inner_func($1);
3899+
end;
3900+
$$ language plpgsql;
3901+
3902+
create or replace function outer_outer_func(int)
3903+
returns int as $$
3904+
begin
3905+
return outer_func($1);
3906+
end;
3907+
$$ language plpgsql;
3908+
3909+
select outer_outer_func(10);
3910+
-- repeated call should to work
3911+
select outer_outer_func(20);
3912+
3913+
drop function outer_outer_func(int);
3914+
drop function outer_func(int);
3915+
drop function inner_func(int);

0 commit comments

Comments
 (0)