Different gettext domain needed for error context

Started by Heikki Linnakangasalmost 14 years ago5 messages
#1Heikki Linnakangas
heikki.linnakangas@enterprisedb.com

I just noticed that we use the same gettext domain for all messages
attached to one error. That is wrong in case of context information,
where you have a stack of context lines, originating from different
modules. The result is that context lines don't always get translated.

For example:

postgres=# set lc_messages ='de_DE.utf8';
SET
postgres=# do $$
begin
select 1 / 0;
end
$$;
FEHLER: Division durch Null
CONTEXT: SQL-Anweisung �select 1 / 0�
PL/pgSQL function "inline_code_block" line 3 at SQL-Anweisung

Notice how the string "PL/pgSQL function ..." is not translated. The
ereport call that raises that error is in int4div, which is in the
backend gettext domain, "postgres". But the errcontext() call comes from
pl_exec.c.

If the error originates from src/pl/plpgsql, then it works:

postgres=# do $$
begin
raise;
end
$$;
FEHLER: RAISE ohne Parameter kann nicht au�erhalb einer
Ausnahmebehandlung verwendet werden
CONTEXT: PL/pgSQL-Funktion �inline_code_block� Zeile 3 bei RAISE

In theory, I guess the other fields like errhint() potentially have the
same problem, but they're not currently used to provide extra
information to messages originating from another module, so I'm inclined
to leave them alone for now.

To fix this, we need to somehow pass the caller's text domain to
errcontext(). The most straightforward way is to pass it as an extra
argument. Ideally, errcontext() would be a macro that passes TEXTDOMAIN
to the underlying function, so that you don't need to change all the
callers of errcontext():

#define errcontext(...) errcontext_domain(TEXTDOMAIN, ...)

But that doesn't work, because it would require varags macros. Anyone
know a trick to make that work?

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Heikki Linnakangas (#1)
Re: Different gettext domain needed for error context

Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:

To fix this, we need to somehow pass the caller's text domain to
errcontext(). The most straightforward way is to pass it as an extra
argument. Ideally, errcontext() would be a macro that passes TEXTDOMAIN
to the underlying function, so that you don't need to change all the
callers of errcontext():

#define errcontext(...) errcontext_domain(TEXTDOMAIN, ...)

But that doesn't work, because it would require varags macros. Anyone
know a trick to make that work?

This is pretty ugly, but AFAICS it should work:

#define errcontext set_errcontext_domain(TEXTDOMAIN), real_errcontext

But it might be better in the long run to bite the bullet and make
people change all their errcontext calls. There aren't that many,
especially not outside the core code.

regards, tom lane

#3Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Tom Lane (#2)
1 attachment(s)
Re: Different gettext domain needed for error context

On 15.02.2012 20:13, Tom Lane wrote:

Heikki Linnakangas<heikki.linnakangas@enterprisedb.com> writes:

To fix this, we need to somehow pass the caller's text domain to
errcontext(). The most straightforward way is to pass it as an extra
argument. Ideally, errcontext() would be a macro that passes TEXTDOMAIN
to the underlying function, so that you don't need to change all the
callers of errcontext():

#define errcontext(...) errcontext_domain(TEXTDOMAIN, ...)

But that doesn't work, because it would require varags macros. Anyone
know a trick to make that work?

This is pretty ugly, but AFAICS it should work:

#define errcontext set_errcontext_domain(TEXTDOMAIN), real_errcontext

But it might be better in the long run to bite the bullet and make
people change all their errcontext calls. There aren't that many,
especially not outside the core code.

Agreed, and not many of the external modules are translated, anyway.

I played with a few different approaches:

1. Add a variant of errcontext() that takes a text domain argument, so
that the calls look like:

errcontext_domain(TEXTDOMAIN, "PL/Perl anonymous code block");

Straightforward, but it looks silly to have to pass TEXTDOMAIN as an
explicit argument. It's not usually explicitly used in C code at all.

2. Add a new field to ErrorContextCallback struct, indicating the
domain. So to set up a callback, you'd do:

ErrorContextCallback pl_error_context;

/* Set up a callback for error reporting */
pl_error_context.callback = plperl_inline_callback;
pl_error_context.previous = error_context_stack;
pl_error_context.arg = (Datum) 0;
pl_error_context.domain = TEXTDOMAIN;
error_context_stack = &pl_error_context;

A variant of this is to encapsulate that boilerplate code to a new macro
or function, so that you'd do just:

push_error_context(&pl_err_context, plperl_inline_callback, (Datum) 0);

push_error_context macro would automatically set the domain to
TEXTDOMAIN, similar to what ereport() does.

A problem with this approach is that if we add a new field to the
struct, any existing code would leave it uninitialized. That could
easily lead to mysterious crashes.

3. In the callback function, call a new function to set the domain to be
used for the errcontext() calls in that callback:

/* use the right domain to translate the errcontext() calls */
set_errtextdomain();

errcontext("PL/Perl anonymous code block");

Attached is a patch using this approach, I like it the most. Existing
code still works, as far as it works today, and it's easy to add that
set_errtextdomain() call to fix callbacks that have translated message.

Barring objections, I'll commit this.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

Attachments:

errcontext-domain-1.patchtext/x-diff; name=errcontext-domain-1.patchDownload
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 0c301b2..e267ff2 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -9913,6 +9913,9 @@ rm_redo_error_callback(void *arg)
 	XLogRecord *record = (XLogRecord *) arg;
 	StringInfoData buf;
 
+	/* use the right domain to translate the errcontext() calls */
+	set_errtextdomain();
+
 	initStringInfo(&buf);
 	RmgrTable[record->xl_rmid].rm_desc(&buf,
 									   record->xl_info,
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 1fffe1c..59ff1c9 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -914,6 +914,9 @@ sql_function_parse_error_callback(void *arg)
 {
 	parse_error_callback_arg *callback_arg = (parse_error_callback_arg *) arg;
 
+	/* use the right domain to translate the errcontext() calls */
+	set_errtextdomain();
+
 	/* See if it's a syntax error; if so, transpose to CREATE FUNCTION */
 	if (!function_parse_error_transpose(callback_arg->prosrc))
 	{
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 95fec8d..8533914 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1743,6 +1743,9 @@ CopyFromErrorCallback(void *arg)
 {
 	CopyState	cstate = (CopyState) arg;
 
+	/* use the right domain to translate the errcontext() calls */
+	set_errtextdomain();
+
 	if (cstate->binary)
 	{
 		/* can't usefully display the data */
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index ae8d374..7643733 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -1294,6 +1294,9 @@ sql_exec_error_callback(void *arg)
 	if (fcache == NULL || fcache->fname == NULL)
 		return;
 
+	/* use the right domain to translate the errcontext() calls */
+	set_errtextdomain();
+
 	/*
 	 * If there is a syntax error position, convert to internal syntax error
 	 */
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 5e4ae42..fd43106 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -2154,7 +2154,10 @@ _SPI_error_callback(void *arg)
 		internalerrquery(query);
 	}
 	else
+	{
+		set_errtextdomain();
 		errcontext("SQL statement \"%s\"", query);
+	}
 }
 
 /*
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 344ebb7..c747bb6 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4350,6 +4350,9 @@ sql_inline_error_callback(void *arg)
 	inline_error_callback_arg *callback_arg = (inline_error_callback_arg *) arg;
 	int			syntaxerrposition;
 
+	/* use the right domain to translate the errcontext() calls */
+	set_errtextdomain();
+
 	/* If it's a syntax error, convert to internal syntax error report */
 	syntaxerrposition = geterrposition();
 	if (syntaxerrposition > 0)
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 5314954..dcbc275 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -644,6 +644,9 @@ pts_error_callback(void *arg)
 {
 	const char *str = (const char *) arg;
 
+	/* use the right domain to translate the errcontext() calls */
+	set_errtextdomain();
+
 	errcontext("invalid type name \"%s\"", str);
 
 	/*
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 613d754..6630755 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -2849,6 +2849,9 @@ shared_buffer_write_error_callback(void *arg)
 {
 	volatile BufferDesc *bufHdr = (volatile BufferDesc *) arg;
 
+	/* use the right domain to translate the errcontext() calls */
+	set_errtextdomain();
+
 	/* Buffer is pinned, so we can read the tag without locking the spinlock */
 	if (bufHdr != NULL)
 	{
@@ -2868,6 +2871,9 @@ local_buffer_write_error_callback(void *arg)
 {
 	volatile BufferDesc *bufHdr = (volatile BufferDesc *) arg;
 
+	/* use the right domain to translate the errcontext() calls */
+	set_errtextdomain();
+
 	if (bufHdr != NULL)
 	{
 		char	   *path = relpathbackend(bufHdr->tag.rnode, MyBackendId,
diff --git a/src/backend/tsearch/ts_locale.c b/src/backend/tsearch/ts_locale.c
index 5b42a93..b2511bf 100644
--- a/src/backend/tsearch/ts_locale.c
+++ b/src/backend/tsearch/ts_locale.c
@@ -166,6 +166,9 @@ tsearch_readline_callback(void *arg)
 {
 	tsearch_readline_state *stp = (tsearch_readline_state *) arg;
 
+	/* use the right domain to translate the errcontext() calls */
+	set_errtextdomain();
+
 	/*
 	 * We can't include the text of the config line for errors that occur
 	 * during t_readline() itself.	This is only partly a consequence of our
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 239ac19..3d17036 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -395,6 +395,7 @@ errfinish(int dummy,...)
 	int			elevel = edata->elevel;
 	MemoryContext oldcontext;
 	ErrorContextCallback *econtext;
+	const char *olddomain;
 
 	recursion_depth++;
 	CHECK_STACK_DEPTH();
@@ -410,10 +411,23 @@ errfinish(int dummy,...)
 	 * functions will be treated as recursive errors --- this ensures we will
 	 * avoid infinite recursion (see errstart).
 	 */
+	olddomain = edata->domain;
 	for (econtext = error_context_stack;
 		 econtext != NULL;
 		 econtext = econtext->previous)
+	{
+		/*
+		 * Callback can set the text domain, so restore it after the call.
+		 * This isn't strictly necessary, every callback should set the domain
+		 * explicitly if the messages are translatable to begin with. But
+		 * just in case a callback forgets to do it (perhaps because it's a
+		 * an external module that was written before version 9.2, where
+		 * errtextdomain() was added), let's be consistent and always use the
+		 * domain specified in errstart.
+		 */
 		(*econtext->callback) (econtext->arg);
+		edata->domain = olddomain;
+	}
 
 	/*
 	 * If ERROR (not more nor less) we pass it off to the current handler.
@@ -980,6 +994,34 @@ errcontext(const char *fmt,...)
 
 
 /*
+ * errtextdomain --- set the message domain used for errcontext calls
+ *
+ * This is used in error callback functions, before calling errcontext(). It
+ * sets the message domain that will be used to translate the messages in
+ * the errcontext() call(s) that follow.  Normally we set the domain in
+ * errstart(), but an error context callback often resides in a different
+ * module than where the error originates and where errstart() called, so the
+ * text domain needs to be set explicitly in the callback.
+ *
+ * Actually, this affects not only errcontext() calls but also errmsg(),
+ * errdetail() etc, but errcontext() is the only function that is normally
+ * called at a distance from the original ereport() site.
+ */
+int
+errtextdomain(const char *domain)
+{
+	ErrorData  *edata = &errordata[errordata_stack_depth];
+
+	/* we don't bother incrementing recursion_depth */
+	CHECK_STACK_DEPTH();
+
+	edata->domain = domain;
+
+	return 0;					/* return value does not matter */
+}
+
+
+/*
  * errhidestmt --- optionally suppress STATEMENT: field of log entry
  *
  * This should be called if the message text already includes the statement.
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 7b5bcfa..a78b949 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -178,6 +178,8 @@ errcontext(const char *fmt,...)
    the supplied arguments. */
 __attribute__((format(PG_PRINTF_ATTRIBUTE, 1, 2)));
 
+extern int errtextdomain(const char *domain);
+
 extern int	errhidestmt(bool hide_stmt);
 
 extern int	errfunction(const char *funcname);
@@ -227,6 +229,15 @@ typedef struct ErrorContextCallback
 
 extern PGDLLIMPORT ErrorContextCallback *error_context_stack;
 
+/*
+ * This is a convenience macro to set text-domain to the default for the
+ * current source file. This should be called in every error callback
+ * function, before calling errcontext(), to make sure the right gettext
+ * domain is used to translate the context message.
+ */
+#define set_errtextdomain()	\
+	errtextdomain(TEXTDOMAIN)
+
 
 /*----------
  * API for catching ereport(ERROR) exits.  Use these macros like so:
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index 7a92f3d..22006e3 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -3649,7 +3649,10 @@ plperl_exec_callback(void *arg)
 	char	   *procname = (char *) arg;
 
 	if (procname)
+	{
+		set_errtextdomain();
 		errcontext("PL/Perl function \"%s\"", procname);
+	}
 }
 
 /*
@@ -3661,7 +3664,10 @@ plperl_compile_callback(void *arg)
 	char	   *procname = (char *) arg;
 
 	if (procname)
+	{
+		set_errtextdomain();
 		errcontext("compilation of PL/Perl function \"%s\"", procname);
+	}
 }
 
 /*
@@ -3670,6 +3676,7 @@ plperl_compile_callback(void *arg)
 static void
 plperl_inline_callback(void *arg)
 {
+	set_errtextdomain();
 	errcontext("PL/Perl anonymous code block");
 }
 
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index d43b8e0..8e53f04 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -908,8 +908,11 @@ plpgsql_compile_error_callback(void *arg)
 	}
 
 	if (plpgsql_error_funcname)
+	{
+		set_errtextdomain();
 		errcontext("compilation of PL/pgSQL function \"%s\" near line %d",
 				   plpgsql_error_funcname, plpgsql_latest_lineno());
+	}
 }
 
 
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index a385b9a..a5b9866 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -786,6 +786,9 @@ plpgsql_exec_error_callback(void *arg)
 	if (estate->err_text == raise_skip_msg)
 		return;
 
+	/* use the right domain to translate the errcontext() calls */
+	set_errtextdomain();
+
 	if (estate->err_text != NULL)
 	{
 		/*
diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c
index 280d3ed..0356d56 100644
--- a/src/pl/plpython/plpy_exec.c
+++ b/src/pl/plpython/plpy_exec.c
@@ -458,7 +458,10 @@ plpython_return_error_callback(void *arg)
 	PLyExecutionContext *exec_ctx = PLy_current_execution_context();
 
 	if (exec_ctx->curr_proc)
+	{
+		set_errtextdomain();
 		errcontext("while creating return value");
+	}
 }
 
 static PyObject *
@@ -786,7 +789,10 @@ plpython_trigger_error_callback(void *arg)
 	PLyExecutionContext *exec_ctx = PLy_current_execution_context();
 
 	if (exec_ctx->curr_proc)
+	{
+		set_errtextdomain();
 		errcontext("while modifying trigger row");
+	}
 }
 
 /* execute Python code, propagate Python errors to the backend */
diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c
index c126db9..da7c8ed 100644
--- a/src/pl/plpython/plpy_main.c
+++ b/src/pl/plpython/plpy_main.c
@@ -343,13 +343,17 @@ plpython_error_callback(void *arg)
 	PLyExecutionContext *exec_ctx = PLy_current_execution_context();
 
 	if (exec_ctx->curr_proc)
+	{
+		set_errtextdomain();
 		errcontext("PL/Python function \"%s\"",
 				   PLy_procedure_name(exec_ctx->curr_proc));
+	}
 }
 
 static void
 plpython_inline_error_callback(void *arg)
 {
+	set_errtextdomain();
 	errcontext("PL/Python anonymous code block");
 }
 
#4Tom Lane
tgl@sss.pgh.pa.us
In reply to: Heikki Linnakangas (#3)
Re: Different gettext domain needed for error context

Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:

3. In the callback function, call a new function to set the domain to be
used for the errcontext() calls in that callback:

/* use the right domain to translate the errcontext() calls */
set_errtextdomain();

errcontext("PL/Perl anonymous code block");

Attached is a patch using this approach, I like it the most.

I don't like it at all. It seems horridly error-prone to me: there
*will* be sins of omission all over the place.

I really think we need to change errcontext itself to pass the correct
domain. If we are going to require a domain to be provided (and this
does require that, for correct operation), then we need to break any
code that doesn't provide it in a visible fashion.

A possibly more attractive alternative is to redefine errcontext
with a macro that allows TEXTDOMAIN to be passed in behind-the-scenes,
thus keeping source-level compatibility. We can do this with the same
type of hack we've used for many years for elog():

#define errcontext set_errcontext_domain(TEXTDOMAIN), errcontext_msg

where the actual message-passing function is now called errcontext_msg.

regards, tom lane

#5Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Tom Lane (#4)
1 attachment(s)
Re: Different gettext domain needed for error context

On 15.04.2012 00:54, Tom Lane wrote:

I really think we need to change errcontext itself to pass the correct
domain. If we are going to require a domain to be provided (and this
does require that, for correct operation), then we need to break any
code that doesn't provide it in a visible fashion.

A possibly more attractive alternative is to redefine errcontext
with a macro that allows TEXTDOMAIN to be passed in behind-the-scenes,
thus keeping source-level compatibility. We can do this with the same
type of hack we've used for many years for elog():

#define errcontext set_errcontext_domain(TEXTDOMAIN), errcontext_msg

where the actual message-passing function is now called errcontext_msg.

Ok then, here's a patch using that approach.

I had to rename a few local variables called "errcontext", because the
macro now tries to expands those and you get an error.

Note: If you want to test this at home, the original test case I posted
doesn't currently work because the text of the context messages in
PL/pgSQL have been slightly changed since I posted the original test
case, but the translations have not been updated yet. Until then, you
can manually remove the double quotes in messages like 'function \"%s\"'
in the .po file to test this.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

Attachments:

errcontext-domain-2.patchtext/x-diff; name=errcontext-domain-2.patchDownload
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 0c301b2..a10b569 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6504,7 +6504,7 @@ StartupXLOG(void)
 			bool		recoveryContinue = true;
 			bool		recoveryApply = true;
 			bool		recoveryPause = false;
-			ErrorContextCallback errcontext;
+			ErrorContextCallback errcallback;
 			TimestampTz xtime;
 
 			InRedo = true;
@@ -6566,10 +6566,10 @@ StartupXLOG(void)
 				}
 
 				/* Setup error traceback support for ereport() */
-				errcontext.callback = rm_redo_error_callback;
-				errcontext.arg = (void *) record;
-				errcontext.previous = error_context_stack;
-				error_context_stack = &errcontext;
+				errcallback.callback = rm_redo_error_callback;
+				errcallback.arg = (void *) record;
+				errcallback.previous = error_context_stack;
+				error_context_stack = &errcallback;
 
 				/*
 				 * ShmemVariableCache->nextXid must be beyond record's xid.
@@ -6614,7 +6614,7 @@ StartupXLOG(void)
 				RmgrTable[record->xl_rmid].rm_redo(EndRecPtr, record);
 
 				/* Pop the error context stack */
-				error_context_stack = errcontext.previous;
+				error_context_stack = errcallback.previous;
 
 				if (!XLogRecPtrIsInvalid(ControlFile->backupStartPoint) &&
 					XLByteLE(ControlFile->backupEndPoint, EndRecPtr))
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 95fec8d..cb7e67a 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1854,7 +1854,7 @@ CopyFrom(CopyState cstate)
 	TupleTableSlot *myslot;
 	MemoryContext oldcontext = CurrentMemoryContext;
 
-	ErrorContextCallback errcontext;
+	ErrorContextCallback errcallback;
 	CommandId	mycid = GetCurrentCommandId(true);
 	int			hi_options = 0; /* start with default heap_insert options */
 	BulkInsertState bistate;
@@ -1998,10 +1998,10 @@ CopyFrom(CopyState cstate)
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
-	errcontext.callback = CopyFromErrorCallback;
-	errcontext.arg = (void *) cstate;
-	errcontext.previous = error_context_stack;
-	error_context_stack = &errcontext;
+	errcallback.callback = CopyFromErrorCallback;
+	errcallback.arg = (void *) cstate;
+	errcallback.previous = error_context_stack;
+	error_context_stack = &errcallback;
 
 	for (;;)
 	{
@@ -2116,7 +2116,7 @@ CopyFrom(CopyState cstate)
 							nBufferedTuples, bufferedTuples);
 
 	/* Done, clean up */
-	error_context_stack = errcontext.previous;
+	error_context_stack = errcallback.previous;
 
 	FreeBulkInsertState(bistate);
 
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index 80dbdd1..33966ee 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -144,10 +144,10 @@ setup_parser_errposition_callback(ParseCallbackState *pcbstate,
 	/* Setup error traceback support for ereport() */
 	pcbstate->pstate = pstate;
 	pcbstate->location = location;
-	pcbstate->errcontext.callback = pcb_error_callback;
-	pcbstate->errcontext.arg = (void *) pcbstate;
-	pcbstate->errcontext.previous = error_context_stack;
-	error_context_stack = &pcbstate->errcontext;
+	pcbstate->errcallback.callback = pcb_error_callback;
+	pcbstate->errcallback.arg = (void *) pcbstate;
+	pcbstate->errcallback.previous = error_context_stack;
+	error_context_stack = &pcbstate->errcallback;
 }
 
 /*
@@ -157,7 +157,7 @@ void
 cancel_parser_errposition_callback(ParseCallbackState *pcbstate)
 {
 	/* Pop the error context stack */
-	error_context_stack = pcbstate->errcontext.previous;
+	error_context_stack = pcbstate->errcallback.previous;
 }
 
 /*
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 613d754..35c8040 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -1888,7 +1888,7 @@ static void
 FlushBuffer(volatile BufferDesc *buf, SMgrRelation reln)
 {
 	XLogRecPtr	recptr;
-	ErrorContextCallback errcontext;
+	ErrorContextCallback errcallback;
 	instr_time	io_start,
 				io_time;
 
@@ -1901,10 +1901,10 @@ FlushBuffer(volatile BufferDesc *buf, SMgrRelation reln)
 		return;
 
 	/* Setup error traceback support for ereport() */
-	errcontext.callback = shared_buffer_write_error_callback;
-	errcontext.arg = (void *) buf;
-	errcontext.previous = error_context_stack;
-	error_context_stack = &errcontext;
+	errcallback.callback = shared_buffer_write_error_callback;
+	errcallback.arg = (void *) buf;
+	errcallback.previous = error_context_stack;
+	error_context_stack = &errcallback;
 
 	/* Find smgr relation for buffer, and mark it as transient */
 	if (reln == NULL)
@@ -1970,7 +1970,7 @@ FlushBuffer(volatile BufferDesc *buf, SMgrRelation reln)
 									   reln->smgr_rnode.node.relNode);
 
 	/* Pop the error context stack */
-	error_context_stack = errcontext.previous;
+	error_context_stack = errcallback.previous;
 }
 
 /*
@@ -2189,13 +2189,13 @@ FlushRelationBuffers(Relation rel)
 			if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
 				(bufHdr->flags & BM_VALID) && (bufHdr->flags & BM_DIRTY))
 			{
-				ErrorContextCallback errcontext;
+				ErrorContextCallback errcallback;
 
 				/* Setup error traceback support for ereport() */
-				errcontext.callback = local_buffer_write_error_callback;
-				errcontext.arg = (void *) bufHdr;
-				errcontext.previous = error_context_stack;
-				error_context_stack = &errcontext;
+				errcallback.callback = local_buffer_write_error_callback;
+				errcallback.arg = (void *) bufHdr;
+				errcallback.previous = error_context_stack;
+				error_context_stack = &errcallback;
 
 				smgrwrite(rel->rd_smgr,
 						  bufHdr->tag.forkNum,
@@ -2206,7 +2206,7 @@ FlushRelationBuffers(Relation rel)
 				bufHdr->flags &= ~(BM_DIRTY | BM_JUST_DIRTIED);
 
 				/* Pop the error context stack */
-				error_context_stack = errcontext.previous;
+				error_context_stack = errcallback.previous;
 			}
 		}
 
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 239ac19..1fbb10e 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -683,13 +683,13 @@ errcode_for_socket_access(void)
  * to the edata field because the buffer might be considerably larger than
  * really necessary.
  */
-#define EVALUATE_MESSAGE(targetfield, appendval, translateit)  \
+#define EVALUATE_MESSAGE(domain, targetfield, appendval, translateit)	\
 	{ \
 		char		   *fmtbuf; \
 		StringInfoData	buf; \
 		/* Internationalize the error format string */ \
 		if (translateit && !in_error_recursion_trouble()) \
-			fmt = dgettext(edata->domain, fmt); \
+			fmt = dgettext((domain), fmt);				  \
 		/* Expand %m in format string */ \
 		fmtbuf = expand_fmt_string(fmt, edata); \
 		initStringInfo(&buf); \
@@ -723,14 +723,14 @@ errcode_for_socket_access(void)
  * must be declared like "const char *fmt_singular, const char *fmt_plural,
  * unsigned long n, ...".  Translation is assumed always wanted.
  */
-#define EVALUATE_MESSAGE_PLURAL(targetfield, appendval)  \
+#define EVALUATE_MESSAGE_PLURAL(domain, targetfield, appendval)  \
 	{ \
 		const char	   *fmt; \
 		char		   *fmtbuf; \
 		StringInfoData	buf; \
 		/* Internationalize the error format string */ \
 		if (!in_error_recursion_trouble()) \
-			fmt = dngettext(edata->domain, fmt_singular, fmt_plural, n); \
+			fmt = dngettext((domain), fmt_singular, fmt_plural, n);	\
 		else \
 			fmt = (n == 1 ? fmt_singular : fmt_plural); \
 		/* Expand %m in format string */ \
@@ -781,7 +781,7 @@ errmsg(const char *fmt,...)
 	CHECK_STACK_DEPTH();
 	oldcontext = MemoryContextSwitchTo(ErrorContext);
 
-	EVALUATE_MESSAGE(message, false, true);
+	EVALUATE_MESSAGE(edata->domain, message, false, true);
 
 	MemoryContextSwitchTo(oldcontext);
 	recursion_depth--;
@@ -810,7 +810,7 @@ errmsg_internal(const char *fmt,...)
 	CHECK_STACK_DEPTH();
 	oldcontext = MemoryContextSwitchTo(ErrorContext);
 
-	EVALUATE_MESSAGE(message, false, false);
+	EVALUATE_MESSAGE(edata->domain, message, false, false);
 
 	MemoryContextSwitchTo(oldcontext);
 	recursion_depth--;
@@ -833,7 +833,7 @@ errmsg_plural(const char *fmt_singular, const char *fmt_plural,
 	CHECK_STACK_DEPTH();
 	oldcontext = MemoryContextSwitchTo(ErrorContext);
 
-	EVALUATE_MESSAGE_PLURAL(message, false);
+	EVALUATE_MESSAGE_PLURAL(edata->domain, message, false);
 
 	MemoryContextSwitchTo(oldcontext);
 	recursion_depth--;
@@ -854,7 +854,7 @@ errdetail(const char *fmt,...)
 	CHECK_STACK_DEPTH();
 	oldcontext = MemoryContextSwitchTo(ErrorContext);
 
-	EVALUATE_MESSAGE(detail, false, true);
+	EVALUATE_MESSAGE(edata->domain, detail, false, true);
 
 	MemoryContextSwitchTo(oldcontext);
 	recursion_depth--;
@@ -881,7 +881,7 @@ errdetail_internal(const char *fmt,...)
 	CHECK_STACK_DEPTH();
 	oldcontext = MemoryContextSwitchTo(ErrorContext);
 
-	EVALUATE_MESSAGE(detail, false, false);
+	EVALUATE_MESSAGE(edata->domain, detail, false, false);
 
 	MemoryContextSwitchTo(oldcontext);
 	recursion_depth--;
@@ -902,7 +902,7 @@ errdetail_log(const char *fmt,...)
 	CHECK_STACK_DEPTH();
 	oldcontext = MemoryContextSwitchTo(ErrorContext);
 
-	EVALUATE_MESSAGE(detail_log, false, true);
+	EVALUATE_MESSAGE(edata->domain, detail_log, false, true);
 
 	MemoryContextSwitchTo(oldcontext);
 	recursion_depth--;
@@ -925,7 +925,7 @@ errdetail_plural(const char *fmt_singular, const char *fmt_plural,
 	CHECK_STACK_DEPTH();
 	oldcontext = MemoryContextSwitchTo(ErrorContext);
 
-	EVALUATE_MESSAGE_PLURAL(detail, false);
+	EVALUATE_MESSAGE_PLURAL(edata->domain, detail, false);
 
 	MemoryContextSwitchTo(oldcontext);
 	recursion_depth--;
@@ -946,7 +946,7 @@ errhint(const char *fmt,...)
 	CHECK_STACK_DEPTH();
 	oldcontext = MemoryContextSwitchTo(ErrorContext);
 
-	EVALUATE_MESSAGE(hint, false, true);
+	EVALUATE_MESSAGE(edata->domain, hint, false, true);
 
 	MemoryContextSwitchTo(oldcontext);
 	recursion_depth--;
@@ -955,14 +955,14 @@ errhint(const char *fmt,...)
 
 
 /*
- * errcontext --- add a context error message text to the current error
+ * errcontext_msg --- add a context error message text to the current error
  *
  * Unlike other cases, multiple calls are allowed to build up a stack of
  * context information.  We assume earlier calls represent more-closely-nested
  * states.
  */
 int
-errcontext(const char *fmt,...)
+errcontext_msg(const char *fmt,...)
 {
 	ErrorData  *edata = &errordata[errordata_stack_depth];
 	MemoryContext oldcontext;
@@ -971,13 +971,35 @@ errcontext(const char *fmt,...)
 	CHECK_STACK_DEPTH();
 	oldcontext = MemoryContextSwitchTo(ErrorContext);
 
-	EVALUATE_MESSAGE(context, true, true);
+	EVALUATE_MESSAGE(edata->context_domain, context, true, true);
 
 	MemoryContextSwitchTo(oldcontext);
 	recursion_depth--;
 	return 0;					/* return value does not matter */
 }
 
+/*
+ * set_errcontext_domain --- set message domain to be used by errcontext()
+ *
+ * errcontext_msg() can be called from a different module than the original
+ * ereport(), so we cannot use the message domain passed in errstart() to
+ * translate it.  Instead, each errcontext_msg() call should be preceded by
+ * a set_errcontext_domain() call to specify the domain.  This is usually
+ * done transparently by the errcontext() macro.
+ */
+int
+set_errcontext_domain(const char *domain)
+{
+	ErrorData  *edata = &errordata[errordata_stack_depth];
+
+	/* we don't bother incrementing recursion_depth */
+	CHECK_STACK_DEPTH();
+
+	edata->context_domain = domain;
+
+	return 0;					/* return value does not matter */
+}
+
 
 /*
  * errhidestmt --- optionally suppress STATEMENT: field of log entry
@@ -1201,7 +1223,7 @@ elog_finish(int elevel, const char *fmt,...)
 	recursion_depth++;
 	oldcontext = MemoryContextSwitchTo(ErrorContext);
 
-	EVALUATE_MESSAGE(message, false, false);
+	EVALUATE_MESSAGE(edata->domain, message, false, false);
 
 	MemoryContextSwitchTo(oldcontext);
 	recursion_depth--;
@@ -1260,7 +1282,7 @@ format_elog_string(const char *fmt,...)
 
 	oldcontext = MemoryContextSwitchTo(ErrorContext);
 
-	EVALUATE_MESSAGE(message, false, true);
+	EVALUATE_MESSAGE(edata->domain, message, false, true);
 
 	MemoryContextSwitchTo(oldcontext);
 
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 670e084..1b0f0dd 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -126,7 +126,7 @@ typedef struct ParseCallbackState
 {
 	ParseState *pstate;
 	int			location;
-	ErrorContextCallback errcontext;
+	ErrorContextCallback errcallback;
 } ParseCallbackState;
 
 
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 7b5bcfa..0c0c935 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -172,8 +172,20 @@ errhint(const char *fmt,...)
    the supplied arguments. */
 __attribute__((format(PG_PRINTF_ATTRIBUTE, 1, 2)));
 
+/*
+ * errcontext() is typically called in error context callback functions, not
+ * within the ereport() macro. The callback function can be in a completely
+ * different module than the ereport() call, so the message domain passed
+ * in errstart() cannot be used to translate the context message.
+ *
+ * set_errcontext_domain() first sets the domain to be used, and
+ * errcontext_msg() passes the actual message.
+ */
+#define errcontext	set_errcontext_domain(TEXTDOMAIN),  errcontext_msg
+
+extern int	set_errcontext_domain(const char *domain);
 extern int
-errcontext(const char *fmt,...)
+errcontext_msg(const char *fmt,...)
 /* This extension allows gcc to check the format string for consistency with
    the supplied arguments. */
 __attribute__((format(PG_PRINTF_ATTRIBUTE, 1, 2)));
@@ -315,6 +327,7 @@ typedef struct ErrorData
 	int			lineno;			/* __LINE__ of ereport() call */
 	const char *funcname;		/* __func__ of ereport() call */
 	const char *domain;			/* message domain */
+	const char *context_domain;	/* message domain for context message */
 	int			sqlerrcode;		/* encoded ERRSTATE */
 	char	   *message;		/* primary error message */
 	char	   *detail;			/* detail error message */