*** a/src/backend/utils/error/elog.c --- b/src/backend/utils/error/elog.c *************** *** 3124,3126 **** trace_recovery(int trace_level) --- 3124,3180 ---- 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 stack to empty. The only case where it would be more than one + * deep is if we serviced an error that interrupted construction of + * another message. We assume control escaped out of that message + * construction and won't ever go back. + */ + errordata_stack_depth = -1; + recursion_depth = 0; + /* Delete all data in ErrorContext */ + MemoryContextResetAndDeleteChildren(ErrorContext); + + 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 *************** *** 1599,1604 **** exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt) --- 1599,1616 ---- estate->cur_error->schema_name); 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 *************** *** 301,306 **** static List *read_raise_options(void); --- 301,307 ---- %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 *************** *** 889,894 **** stmt_getdiag : K_GET getdiag_area_opt K_DIAGNOSTICS getdiag_list ';' --- 890,896 ---- /* 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), *************** *** 966,971 **** getdiag_item : --- 968,976 ---- 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, *************** *** 2280,2285 **** unreserved_keyword : --- 2285,2291 ---- | 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 *************** *** 133,138 **** static const ScanKeyword unreserved_keywords[] = { --- 133,139 ---- 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 *************** *** 4897,4899 **** ERROR: value for domain orderedarray violates check constraint "sorted" --- 4897,4947 ---- 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 *************** *** 3880,3882 **** select testoa(1,2,1); -- fail at update --- 3880,3915 ---- 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);