*** 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);