*** a/doc/src/sgml/plpgsql.sgml
--- b/doc/src/sgml/plpgsql.sgml
***************
*** 2712,2717 **** END;
--- 2712,2765 ----
+
+
+ Obtaining information about an calls stack
+
+
+
+
+ GET CURRENT DIAGNOSTICS variable = PG_CONTEXT , ... ;
+
+
+
+ With command GET DIAGNOSTICS and status
+ item PG_CONTEXT> is possible to get line(s) of
+ text describing the call stack. A first row is related to
+ current function and currently executed GET DIAGNOSTICS
+ command. Second row is related to first outer function,
+ and related statement, etc.
+
+
+ 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)
+
+
+
+
*** a/src/backend/utils/error/elog.c
--- b/src/backend/utils/error/elog.c
***************
*** 3120,3122 **** trace_recovery(int trace_level)
--- 3120,3168 ----
return trace_level;
}
+
+ /*
+ * InvokeErrorCallbacks - invoke registrated error callback functions.
+ */
+ char *
+ InvokeErrorCallbacks(void)
+ {
+ ErrorData *edata;
+ ErrorContextCallback *econtext;
+ MemoryContext oldcontext = CurrentMemoryContext;
+ char *result;
+
+ /* this function should not be started in exception handler */
+ Assert(recursion_depth == 0);
+
+ if (++errordata_stack_depth >= ERRORDATA_STACK_SIZE)
+ {
+ 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 short living context */
+ MemoryContextSwitchTo(ErrorContext);
+
+ /*
+ * Call any context callback functions. Errors occurring in callback
+ * functions will be treated as recursive errors --- this ensures we will
+ * avoid infinite recursion (see errstart).
+ */
+ for (econtext = error_context_stack;
+ econtext != NULL;
+ econtext = econtext->previous)
+ (*econtext->callback) (econtext->arg);
+
+ MemoryContextSwitchTo(oldcontext);
+ result = pstrdup(edata->context ? edata->context : "");
+
+ /* Reset error stack */
+ FlushErrorState();
+
+ return result;
+ }
*** a/src/include/utils/elog.h
--- b/src/include/utils/elog.h
***************
*** 406,411 **** extern void FlushErrorState(void);
--- 406,413 ----
extern void ReThrowError(ErrorData *edata) __attribute__((noreturn));
extern void pg_re_throw(void) __attribute__((noreturn));
+ extern char *InvokeErrorCallbacks(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;
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
***************
*** 1574,1579 **** exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt)
--- 1574,1591 ----
estate->cur_error->message);
break;
+ case PLPGSQL_GETDIAG_CONTEXT:
+ {
+ char *context;
+
+ context = InvokeErrorCallbacks();
+
+ exec_assign_c_string(estate, var,
+ context);
+ pfree(context);
+ }
+ break;
+
default:
elog(ERROR, "unrecognized diagnostic item kind: %d",
diag_item->kind);
*** a/src/pl/plpgsql/src/pl_funcs.c
--- b/src/pl/plpgsql/src/pl_funcs.c
***************
*** 273,278 **** plpgsql_getdiag_kindname(int kind)
--- 273,280 ----
{
switch (kind)
{
+ case PLPGSQL_GETDIAG_CONTEXT:
+ return "PG_CONTEXT";
case PLPGSQL_GETDIAG_ROW_COUNT:
return "ROW_COUNT";
case PLPGSQL_GETDIAG_RESULT_OID:
*** a/src/pl/plpgsql/src/pl_gram.y
--- b/src/pl/plpgsql/src/pl_gram.y
***************
*** 298,303 **** static List *read_raise_options(void);
--- 298,304 ----
%token K_OPTION
%token K_OR
%token K_PERFORM
+ %token K_PG_CONTEXT
%token K_PG_EXCEPTION_CONTEXT
%token K_PG_EXCEPTION_DETAIL
%token K_PG_EXCEPTION_HINT
***************
*** 884,889 **** stmt_getdiag : K_GET getdiag_area_opt K_DIAGNOSTICS getdiag_list ';'
--- 885,891 ----
/* 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),
***************
*** 956,961 **** getdiag_item :
--- 958,966 ----
int tok = yylex();
if (tok_is_keyword(tok, &yylval,
+ K_PG_CONTEXT, "pg_context"))
+ $$ = PLPGSQL_GETDIAG_CONTEXT;
+ else if (tok_is_keyword(tok, &yylval,
K_ROW_COUNT, "row_count"))
$$ = PLPGSQL_GETDIAG_ROW_COUNT;
else if (tok_is_keyword(tok, &yylval,
***************
*** 2252,2257 **** unreserved_keyword :
--- 2257,2263 ----
| K_NO
| K_NOTICE
| K_OPTION
+ | K_PG_CONTEXT
| K_PG_EXCEPTION_CONTEXT
| K_PG_EXCEPTION_DETAIL
| K_PG_EXCEPTION_HINT
*** a/src/pl/plpgsql/src/pl_scanner.c
--- b/src/pl/plpgsql/src/pl_scanner.c
***************
*** 130,135 **** static const ScanKeyword unreserved_keywords[] = {
--- 130,136 ----
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_exception_context", K_PG_EXCEPTION_CONTEXT, UNRESERVED_KEYWORD)
PG_KEYWORD("pg_exception_detail", K_PG_EXCEPTION_DETAIL, UNRESERVED_KEYWORD)
PG_KEYWORD("pg_exception_hint", K_PG_EXCEPTION_HINT, UNRESERVED_KEYWORD)
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
***************
*** 122,127 **** enum
--- 122,128 ----
*/
enum
{
+ PLPGSQL_GETDIAG_CONTEXT,
PLPGSQL_GETDIAG_ROW_COUNT,
PLPGSQL_GETDIAG_RESULT_OID,
PLPGSQL_GETDIAG_ERROR_CONTEXT,
*** a/src/test/regress/expected/plpgsql.out
--- b/src/test/regress/expected/plpgsql.out
***************
*** 4863,4865 **** ERROR: value for domain orderedarray violates check constraint "sorted"
--- 4863,4913 ----
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);
*** a/src/test/regress/sql/plpgsql.sql
--- b/src/test/regress/sql/plpgsql.sql
***************
*** 3848,3850 **** select testoa(1,2,1); -- fail at update
--- 3848,3883 ----
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);