Tom, * Tom Lane ([email protected]) wrote: > OK. One possibly non-obvious point is that I think the field should be > defined as "context containing associated non-constant strings"; this > would mean in particular that CopyErrorData would need to change it > to CurrentMemoryContext in the copied struct, and then ReThrowError > would change it back when restoring the data onto the error stack. > This detail is probably a no-op in current usages, but in the future it > might allow modification of a copied ErrorData while it's outside > ErrorContext, if anyone should want to do that. > > Also I'd advise declaring the field as "struct MemoryContextData *" > to avoid having to include palloc.h into elog.h.
Good points, all. Apologies for it taking a bit to get to this, but
please take a look when you get a chance. Barring objections, this is
what I'm planning to commit, which moves most calls to use the new
edata->alloc_context instead of ErrorContext. This also means we can
allow GET DIAG ... PG_CONTEXT to be called from exception handlers,
which I've set up and added regression tests for.
Thanks!
Stephen
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
new file mode 100644
index 036fe09..a812ccd 100644
*** a/src/backend/utils/error/elog.c
--- b/src/backend/utils/error/elog.c
*************** err_gettext(const char *str)
*** 87,93 ****
/* This extension allows gcc to check the format string for consistency with
the supplied arguments. */
__attribute__((format_arg(1)));
! static void set_errdata_field(char **ptr, const char *str);
/* Global variables */
ErrorContextCallback *error_context_stack = NULL;
--- 87,93 ----
/* This extension allows gcc to check the format string for consistency with
the supplied arguments. */
__attribute__((format_arg(1)));
! static void set_errdata_field(MemoryContextData *cxt, char **ptr, const char *str);
/* Global variables */
ErrorContextCallback *error_context_stack = NULL;
*************** errstart(int elevel, const char *filenam
*** 373,378 ****
--- 373,383 ----
/* errno is saved here so that error parameter eval can't change it */
edata->saved_errno = errno;
+ /*
+ * Any allocations for this error state level should go into ErrorContext
+ */
+ edata->assoc_context = ErrorContext;
+
recursion_depth--;
return true;
}
*************** errmsg(const char *fmt,...)
*** 786,792 ****
recursion_depth++;
CHECK_STACK_DEPTH();
! oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(edata->domain, message, false, true);
--- 791,797 ----
recursion_depth++;
CHECK_STACK_DEPTH();
! oldcontext = MemoryContextSwitchTo(edata->assoc_context);
EVALUATE_MESSAGE(edata->domain, message, false, true);
*************** errmsg_internal(const char *fmt,...)
*** 815,821 ****
recursion_depth++;
CHECK_STACK_DEPTH();
! oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(edata->domain, message, false, false);
--- 820,826 ----
recursion_depth++;
CHECK_STACK_DEPTH();
! oldcontext = MemoryContextSwitchTo(edata->assoc_context);
EVALUATE_MESSAGE(edata->domain, message, false, false);
*************** errmsg_plural(const char *fmt_singular,
*** 838,844 ****
recursion_depth++;
CHECK_STACK_DEPTH();
! oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE_PLURAL(edata->domain, message, false);
--- 843,849 ----
recursion_depth++;
CHECK_STACK_DEPTH();
! oldcontext = MemoryContextSwitchTo(edata->assoc_context);
EVALUATE_MESSAGE_PLURAL(edata->domain, message, false);
*************** errdetail(const char *fmt,...)
*** 859,865 ****
recursion_depth++;
CHECK_STACK_DEPTH();
! oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(edata->domain, detail, false, true);
--- 864,870 ----
recursion_depth++;
CHECK_STACK_DEPTH();
! oldcontext = MemoryContextSwitchTo(edata->assoc_context);
EVALUATE_MESSAGE(edata->domain, detail, false, true);
*************** errdetail_internal(const char *fmt,...)
*** 886,892 ****
recursion_depth++;
CHECK_STACK_DEPTH();
! oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(edata->domain, detail, false, false);
--- 891,897 ----
recursion_depth++;
CHECK_STACK_DEPTH();
! oldcontext = MemoryContextSwitchTo(edata->assoc_context);
EVALUATE_MESSAGE(edata->domain, detail, false, false);
*************** errdetail_log(const char *fmt,...)
*** 907,913 ****
recursion_depth++;
CHECK_STACK_DEPTH();
! oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(edata->domain, detail_log, false, true);
--- 912,918 ----
recursion_depth++;
CHECK_STACK_DEPTH();
! oldcontext = MemoryContextSwitchTo(edata->assoc_context);
EVALUATE_MESSAGE(edata->domain, detail_log, false, true);
*************** errdetail_plural(const char *fmt_singula
*** 930,936 ****
recursion_depth++;
CHECK_STACK_DEPTH();
! oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE_PLURAL(edata->domain, detail, false);
--- 935,941 ----
recursion_depth++;
CHECK_STACK_DEPTH();
! oldcontext = MemoryContextSwitchTo(edata->assoc_context);
EVALUATE_MESSAGE_PLURAL(edata->domain, detail, false);
*************** errhint(const char *fmt,...)
*** 951,957 ****
recursion_depth++;
CHECK_STACK_DEPTH();
! oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(edata->domain, hint, false, true);
--- 956,962 ----
recursion_depth++;
CHECK_STACK_DEPTH();
! oldcontext = MemoryContextSwitchTo(edata->assoc_context);
EVALUATE_MESSAGE(edata->domain, hint, false, true);
*************** errcontext_msg(const char *fmt,...)
*** 976,982 ****
recursion_depth++;
CHECK_STACK_DEPTH();
! oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(edata->context_domain, context, true, true);
--- 981,987 ----
recursion_depth++;
CHECK_STACK_DEPTH();
! oldcontext = MemoryContextSwitchTo(edata->assoc_context);
EVALUATE_MESSAGE(edata->context_domain, context, true, true);
*************** internalerrquery(const char *query)
*** 1102,1108 ****
}
if (query)
! edata->internalquery = MemoryContextStrdup(ErrorContext, query);
return 0; /* return value does not matter */
}
--- 1107,1113 ----
}
if (query)
! edata->internalquery = MemoryContextStrdup(edata->assoc_context, query);
return 0; /* return value does not matter */
}
*************** err_generic_string(int field, const char
*** 1128,1146 ****
switch (field)
{
case PG_DIAG_SCHEMA_NAME:
! set_errdata_field(&edata->schema_name, str);
break;
case PG_DIAG_TABLE_NAME:
! set_errdata_field(&edata->table_name, str);
break;
case PG_DIAG_COLUMN_NAME:
! set_errdata_field(&edata->column_name, str);
break;
case PG_DIAG_DATATYPE_NAME:
! set_errdata_field(&edata->datatype_name, str);
break;
case PG_DIAG_CONSTRAINT_NAME:
! set_errdata_field(&edata->constraint_name, str);
break;
default:
elog(ERROR, "unsupported ErrorData field id: %d", field);
--- 1133,1151 ----
switch (field)
{
case PG_DIAG_SCHEMA_NAME:
! set_errdata_field(edata->assoc_context, &edata->schema_name, str);
break;
case PG_DIAG_TABLE_NAME:
! set_errdata_field(edata->assoc_context, &edata->table_name, str);
break;
case PG_DIAG_COLUMN_NAME:
! set_errdata_field(edata->assoc_context, &edata->column_name, str);
break;
case PG_DIAG_DATATYPE_NAME:
! set_errdata_field(edata->assoc_context, &edata->datatype_name, str);
break;
case PG_DIAG_CONSTRAINT_NAME:
! set_errdata_field(edata->assoc_context, &edata->constraint_name, str);
break;
default:
elog(ERROR, "unsupported ErrorData field id: %d", field);
*************** err_generic_string(int field, const char
*** 1154,1163 ****
* set_errdata_field --- set an ErrorData string field
*/
static void
! set_errdata_field(char **ptr, const char *str)
{
Assert(*ptr == NULL);
! *ptr = MemoryContextStrdup(ErrorContext, str);
}
/*
--- 1159,1168 ----
* set_errdata_field --- set an ErrorData string field
*/
static void
! set_errdata_field(MemoryContextData *cxt, char **ptr, const char *str)
{
Assert(*ptr == NULL);
! *ptr = MemoryContextStrdup(cxt, str);
}
/*
*************** elog_start(const char *filename, int lin
*** 1257,1262 ****
--- 1262,1270 ----
edata->funcname = funcname;
/* errno is saved now so that error parameter eval can't change it */
edata->saved_errno = errno;
+
+ /* Use ErrorContext for any allocations done at this level. */
+ edata->assoc_context = ErrorContext;
}
/*
*************** elog_finish(int elevel, const char *fmt,
*** 1282,1288 ****
* Format error message just like errmsg_internal().
*/
recursion_depth++;
! oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(edata->domain, message, false, false);
--- 1290,1296 ----
* Format error message just like errmsg_internal().
*/
recursion_depth++;
! oldcontext = MemoryContextSwitchTo(edata->assoc_context);
EVALUATE_MESSAGE(edata->domain, message, false, false);
*************** EmitErrorReport(void)
*** 1366,1372 ****
recursion_depth++;
CHECK_STACK_DEPTH();
! oldcontext = MemoryContextSwitchTo(ErrorContext);
/*
* Call hook before sending message to log. The hook function is allowed
--- 1374,1380 ----
recursion_depth++;
CHECK_STACK_DEPTH();
! oldcontext = MemoryContextSwitchTo(edata->assoc_context);
/*
* Call hook before sending message to log. The hook function is allowed
*************** CopyErrorData(void)
*** 1446,1451 ****
--- 1454,1462 ----
if (newedata->internalquery)
newedata->internalquery = pstrdup(newedata->internalquery);
+ /* Use the calling context for string allocation */
+ newedata->assoc_context = CurrentMemoryContext;
+
return newedata;
}
*************** ReThrowError(ErrorData *edata)
*** 1563,1568 ****
--- 1574,1582 ----
if (newedata->internalquery)
newedata->internalquery = pstrdup(newedata->internalquery);
+ /* Reset the assoc_context to be ErrorContext */
+ newedata->assoc_context = ErrorContext;
+
recursion_depth--;
PG_RE_THROW();
}
*************** pg_re_throw(void)
*** 1630,1641 ****
* GetErrorContextStack - Return the context stack, for display/diags
*
* Returns a pstrdup'd string in the caller's context which includes the PG
! * call stack. It is the caller's responsibility to ensure this string is
! * pfree'd (or its context cleaned up) when done.
! *
! * Note that this function may *not* be called from any existing error case
! * and is not for error-reporting (use ereport() and friends instead, which
! * will also produce a stack trace).
*
* This information is collected by traversing the error contexts and calling
* each context's callback function, each of which is expected to call
--- 1644,1651 ----
* GetErrorContextStack - Return the context stack, for display/diags
*
* Returns a pstrdup'd string in the caller's context which includes the PG
! * error 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
*************** pg_re_throw(void)
*** 1644,1721 ****
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);
/*
! * This function should never be called from an exception handler and
! * therefore will only ever be the top item on the errordata stack
! * (which is set up so that the calls to the callback functions are
! * able to use it).
! *
! * Better safe than sorry, so double-check that we are not being called
! * from an exception handler.
*/
! if (errordata_stack_depth != -1)
{
errordata_stack_depth = -1; /* make room on stack */
! ereport(PANIC,
! (errmsg_internal("GetErrorContextStack called from exception handler")));
}
/*
! * Initialize data for the top, and only at this point, error frame as the
! * callback functions we're about to call will turn around and call
! * errcontext(), which expects to find a valid errordata stack.
*/
- errordata_stack_depth = 0;
edata = &errordata[errordata_stack_depth];
MemSet(edata, 0, sizeof(ErrorData));
/*
! * Use ErrorContext as a short lived context for calling the callbacks;
! * the callbacks will use it through errcontext() even if we don't call
! * them with it, so we have to clean it up below either way.
*/
! 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 and must
! * never call back to us.
*/
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- note that we should be the only item on the error
! * stack at this point and therefore it's safe to clean up the whole stack.
! * This function is not intended nor able to be called from exception
! * handlers.
*/
! FlushErrorState();
!
! return result;
}
--- 1654,1717 ----
char *
GetErrorContextStack(void)
{
ErrorData *edata;
ErrorContextCallback *econtext;
/*
! * Okay, crank up a stack entry to store the info in.
*/
! recursion_depth++;
!
! if (++errordata_stack_depth >= ERRORDATA_STACK_SIZE)
{
+ /*
+ * Wups, stack not big enough. We treat this as a PANIC condition
+ * because it suggests an infinite loop of errors during error
+ * recovery.
+ */
errordata_stack_depth = -1; /* make room on stack */
! ereport(PANIC, (errmsg_internal("ERRORDATA_STACK_SIZE exceeded")));
}
/*
! * Things look good so far, so initialize our error frame
*/
edata = &errordata[errordata_stack_depth];
MemSet(edata, 0, sizeof(ErrorData));
/*
! * Set up assoc_context to be the caller's context, so any allocations
! * done (which will include edata->context) will use their context.
*/
! edata->assoc_context = CurrentMemoryContext;
/*
* 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, though
! * we double-check above, just in case.
*/
for (econtext = error_context_stack;
econtext != NULL;
econtext = econtext->previous)
(*econtext->callback) (econtext->arg);
/*
! * Clean ourselves off the stack, any allocations done should have been
! * using edata->assoc_context, which we set up earlier to be the caller's
! * context, so we're free to just remove our entry off the stack and
! * decrement recursion depth and exit.
*/
! errordata_stack_depth--;
! recursion_depth--;
/*
! * Return a pointer to the string the caller asked for, which should have
! * been allocated in their context.
*/
! return edata->context;
}
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
new file mode 100644
index add0872..6df0d37 100644
*** a/src/include/utils/elog.h
--- b/src/include/utils/elog.h
*************** typedef struct ErrorData
*** 397,402 ****
--- 397,405 ----
int internalpos; /* cursor index into internalquery */
char *internalquery; /* text of internally-generated query */
int saved_errno; /* errno at entry */
+
+ /* context containing associated non-constant strings */
+ struct MemoryContextData *assoc_context;
} ErrorData;
extern void EmitErrorReport(void);
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
new file mode 100644
index 263abef..325d756 100644
*** a/src/pl/plpgsql/src/pl_gram.y
--- b/src/pl/plpgsql/src/pl_gram.y
*************** stmt_getdiag : K_GET getdiag_area_opt K_
*** 895,901 ****
/* 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),
--- 895,900 ----
*************** stmt_getdiag : K_GET getdiag_area_opt K_
*** 921,926 ****
--- 920,928 ----
plpgsql_getdiag_kindname(ditem->kind)),
parser_errposition(@1)));
break;
+ /* these fields are allowed in either case */
+ case PLPGSQL_GETDIAG_CONTEXT:
+ break;
default:
elog(ERROR, "unrecognized diagnostic item kind: %d",
ditem->kind);
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
new file mode 100644
index b022530..4d89699 100644
*** a/src/test/regress/expected/plpgsql.out
--- b/src/test/regress/expected/plpgsql.out
*************** NOTICE: outer_func() done
*** 4987,4989 ****
--- 4987,5087 ----
drop function outer_outer_func(int);
drop function outer_func(int);
drop function inner_func(int);
+ -- access to call stack from exception
+ create function inner_func(int)
+ returns int as $$
+ declare
+ _context text;
+ sx int := 5;
+ begin
+ begin
+ perform sx / 0;
+ exception
+ when division_by_zero then
+ get diagnostics _context = pg_context;
+ raise notice '***%***', _context;
+ end;
+
+ -- lets do it again, just for fun..
+ get diagnostics _context = pg_context;
+ raise notice '***%***', _context;
+ raise notice 'lets make sure we didnt break anything';
+ return 2 * $1;
+ end;
+ $$ language plpgsql;
+ create or replace function outer_func(int)
+ returns int as $$
+ declare
+ myresult int;
+ begin
+ raise notice 'calling down into inner_func()';
+ myresult := inner_func($1);
+ raise notice 'inner_func() done';
+ return myresult;
+ end;
+ $$ language plpgsql;
+ create or replace function outer_outer_func(int)
+ returns int as $$
+ declare
+ myresult int;
+ begin
+ raise notice 'calling down into outer_func()';
+ myresult := outer_func($1);
+ raise notice 'outer_func() done';
+ return myresult;
+ end;
+ $$ language plpgsql;
+ select outer_outer_func(10);
+ NOTICE: calling down into outer_func()
+ NOTICE: calling down into inner_func()
+ CONTEXT: PL/pgSQL function outer_outer_func(integer) line 6 at assignment
+ NOTICE: ***PL/pgSQL function inner_func(integer) line 10 at GET DIAGNOSTICS
+ PL/pgSQL function outer_func(integer) line 6 at assignment
+ PL/pgSQL function outer_outer_func(integer) line 6 at assignment***
+ CONTEXT: PL/pgSQL function outer_func(integer) line 6 at assignment
+ PL/pgSQL function outer_outer_func(integer) line 6 at assignment
+ NOTICE: ***PL/pgSQL function inner_func(integer) line 15 at GET DIAGNOSTICS
+ PL/pgSQL function outer_func(integer) line 6 at assignment
+ PL/pgSQL function outer_outer_func(integer) line 6 at assignment***
+ CONTEXT: PL/pgSQL function outer_func(integer) line 6 at assignment
+ PL/pgSQL function outer_outer_func(integer) line 6 at assignment
+ NOTICE: lets make sure we didnt break anything
+ CONTEXT: PL/pgSQL function outer_func(integer) line 6 at assignment
+ PL/pgSQL function outer_outer_func(integer) line 6 at assignment
+ NOTICE: inner_func() done
+ CONTEXT: PL/pgSQL function outer_outer_func(integer) line 6 at assignment
+ NOTICE: outer_func() done
+ outer_outer_func
+ ------------------
+ 20
+ (1 row)
+
+ -- repeated call should to work
+ select outer_outer_func(20);
+ NOTICE: calling down into outer_func()
+ NOTICE: calling down into inner_func()
+ CONTEXT: PL/pgSQL function outer_outer_func(integer) line 6 at assignment
+ NOTICE: ***PL/pgSQL function inner_func(integer) line 10 at GET DIAGNOSTICS
+ PL/pgSQL function outer_func(integer) line 6 at assignment
+ PL/pgSQL function outer_outer_func(integer) line 6 at assignment***
+ CONTEXT: PL/pgSQL function outer_func(integer) line 6 at assignment
+ PL/pgSQL function outer_outer_func(integer) line 6 at assignment
+ NOTICE: ***PL/pgSQL function inner_func(integer) line 15 at GET DIAGNOSTICS
+ PL/pgSQL function outer_func(integer) line 6 at assignment
+ PL/pgSQL function outer_outer_func(integer) line 6 at assignment***
+ CONTEXT: PL/pgSQL function outer_func(integer) line 6 at assignment
+ PL/pgSQL function outer_outer_func(integer) line 6 at assignment
+ NOTICE: lets make sure we didnt break anything
+ CONTEXT: PL/pgSQL function outer_func(integer) line 6 at assignment
+ PL/pgSQL function outer_outer_func(integer) line 6 at assignment
+ NOTICE: inner_func() done
+ CONTEXT: PL/pgSQL function outer_outer_func(integer) line 6 at assignment
+ NOTICE: outer_func() done
+ outer_outer_func
+ ------------------
+ 40
+ (1 row)
+
+ drop function outer_outer_func(int);
+ drop function outer_func(int);
+ drop function inner_func(int);
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
new file mode 100644
index e791efa..e1f4b2c 100644
*** a/src/test/regress/sql/plpgsql.sql
--- b/src/test/regress/sql/plpgsql.sql
*************** select outer_outer_func(20);
*** 3927,3929 ****
--- 3927,3985 ----
drop function outer_outer_func(int);
drop function outer_func(int);
drop function inner_func(int);
+
+ -- access to call stack from exception
+ create function inner_func(int)
+ returns int as $$
+ declare
+ _context text;
+ sx int := 5;
+ begin
+ begin
+ perform sx / 0;
+ exception
+ when division_by_zero then
+ get diagnostics _context = pg_context;
+ raise notice '***%***', _context;
+ end;
+
+ -- lets do it again, just for fun..
+ get diagnostics _context = pg_context;
+ raise notice '***%***', _context;
+ raise notice 'lets make sure we didnt break anything';
+ return 2 * $1;
+ end;
+ $$ language plpgsql;
+
+ create or replace function outer_func(int)
+ returns int as $$
+ declare
+ myresult int;
+ begin
+ raise notice 'calling down into inner_func()';
+ myresult := inner_func($1);
+ raise notice 'inner_func() done';
+ return myresult;
+ end;
+ $$ language plpgsql;
+
+ create or replace function outer_outer_func(int)
+ returns int as $$
+ declare
+ myresult int;
+ begin
+ raise notice 'calling down into outer_func()';
+ myresult := outer_func($1);
+ raise notice 'outer_func() done';
+ return myresult;
+ 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);
+
signature.asc
Description: Digital signature
