Commit e3a87b49 authored by Tom Lane's avatar Tom Lane

Re-implement the ereport() macro using __VA_ARGS__.

Now that we require C99, we can depend on __VA_ARGS__ to work, and
revising ereport() to use it has several significant benefits:

* The extra parentheses around the auxiliary function calls are now
optional.  Aside from being a bit less ugly, this removes a common
gotcha for new contributors, because in some cases the compiler errors
you got from forgetting them were unintelligible.

* The auxiliary function calls are now evaluated as a comma expression
list rather than as extra arguments to errfinish().  This means that
compilers can be expected to warn about no-op expressions in the list,
allowing detection of several other common mistakes such as forgetting
to add errmsg(...) when converting an elog() call to ereport().

* Unlike the situation with extra function arguments, comma expressions
are guaranteed to be evaluated left-to-right, so this removes platform
dependency in the order of the auxiliary function calls.  While that
dependency hasn't caused us big problems in the past, this change does
allow dropping some rather shaky assumptions around errcontext() domain
handling.

There's no intention to make wholesale changes of existing ereport
calls, but as proof-of-concept this patch removes the extra parens
from a couple of calls in postgres.c.

While new code can be written either way, code intended to be
back-patched will need to use extra parens for awhile yet.  It seems
worth back-patching this change into v12, so as to reduce the window
where we have to be careful about that by one year.  Hence, this patch
is careful to preserve ABI compatibility; a followup HEAD-only patch
will make some additional simplifications.

Andres Freund and Tom Lane

Discussion: https://postgr.es/m/CA+fd4k6N8EjNvZpM8nme+y+05mz-SM8Z_BgkixzkA34R+ej0Kw@mail.gmail.com
parent cef27ae0
...@@ -103,9 +103,9 @@ less -x4 ...@@ -103,9 +103,9 @@ less -x4
message text. In addition there are optional elements, the most message text. In addition there are optional elements, the most
common of which is an error identifier code that follows the SQL spec's common of which is an error identifier code that follows the SQL spec's
SQLSTATE conventions. SQLSTATE conventions.
<function>ereport</function> itself is just a shell function, that exists <function>ereport</function> itself is just a shell macro, that exists
mainly for the syntactic convenience of making message generation mainly for the syntactic convenience of making message generation
look like a function call in the C source code. The only parameter look like a single function call in the C source code. The only parameter
accepted directly by <function>ereport</function> is the severity level. accepted directly by <function>ereport</function> is the severity level.
The primary message text and any optional message elements are The primary message text and any optional message elements are
generated by calling auxiliary functions, such as <function>errmsg</function>, generated by calling auxiliary functions, such as <function>errmsg</function>,
...@@ -116,36 +116,50 @@ less -x4 ...@@ -116,36 +116,50 @@ less -x4
A typical call to <function>ereport</function> might look like this: A typical call to <function>ereport</function> might look like this:
<programlisting> <programlisting>
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DIVISION_BY_ZERO), errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero"))); errmsg("division by zero"));
</programlisting> </programlisting>
This specifies error severity level <literal>ERROR</literal> (a run-of-the-mill This specifies error severity level <literal>ERROR</literal> (a run-of-the-mill
error). The <function>errcode</function> call specifies the SQLSTATE error code error). The <function>errcode</function> call specifies the SQLSTATE error code
using a macro defined in <filename>src/include/utils/errcodes.h</filename>. The using a macro defined in <filename>src/include/utils/errcodes.h</filename>. The
<function>errmsg</function> call provides the primary message text. Notice the <function>errmsg</function> call provides the primary message text.
extra set of parentheses surrounding the auxiliary function calls &mdash; </para>
these are annoying but syntactically necessary.
<para>
You will also frequently see this older style, with an extra set of
parentheses surrounding the auxiliary function calls:
<programlisting>
ereport(ERROR,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
</programlisting>
The extra parentheses were required
before <productname>PostgreSQL</productname> version 12, but are now
optional.
</para> </para>
<para> <para>
Here is a more complex example: Here is a more complex example:
<programlisting> <programlisting>
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_AMBIGUOUS_FUNCTION), errcode(ERRCODE_AMBIGUOUS_FUNCTION),
errmsg("function %s is not unique", errmsg("function %s is not unique",
func_signature_string(funcname, nargs, func_signature_string(funcname, nargs,
NIL, actual_arg_types)), NIL, actual_arg_types)),
errhint("Unable to choose a best candidate function. " errhint("Unable to choose a best candidate function. "
"You might need to add explicit typecasts."))); "You might need to add explicit typecasts."));
</programlisting> </programlisting>
This illustrates the use of format codes to embed run-time values into This illustrates the use of format codes to embed run-time values into
a message text. Also, an optional <quote>hint</quote> message is provided. a message text. Also, an optional <quote>hint</quote> message is provided.
The auxiliary function calls can be written in any order, but
conventionally <function>errcode</function>
and <function>errmsg</function> appear first.
</para> </para>
<para> <para>
If the severity level is <literal>ERROR</literal> or higher, If the severity level is <literal>ERROR</literal> or higher,
<function>ereport</function> aborts the execution of the user-defined <function>ereport</function> aborts execution of the current query
function and does not return to the caller. If the severity level is and does not return to the caller. If the severity level is
lower than <literal>ERROR</literal>, <function>ereport</function> returns normally. lower than <literal>ERROR</literal>, <function>ereport</function> returns normally.
</para> </para>
...@@ -390,7 +404,7 @@ elog(level, "format string", ...); ...@@ -390,7 +404,7 @@ elog(level, "format string", ...);
</programlisting> </programlisting>
is exactly equivalent to: is exactly equivalent to:
<programlisting> <programlisting>
ereport(level, (errmsg_internal("format string", ...))); ereport(level, errmsg_internal("format string", ...));
</programlisting> </programlisting>
Notice that the SQLSTATE error code is always defaulted, and the message Notice that the SQLSTATE error code is always defaulted, and the message
string is not subject to translation. string is not subject to translation.
......
...@@ -3720,15 +3720,15 @@ process_postgres_switches(int argc, char *argv[], GucContext ctx, ...@@ -3720,15 +3720,15 @@ process_postgres_switches(int argc, char *argv[], GucContext ctx,
/* spell the error message a bit differently depending on context */ /* spell the error message a bit differently depending on context */
if (IsUnderPostmaster) if (IsUnderPostmaster)
ereport(FATAL, ereport(FATAL,
(errcode(ERRCODE_SYNTAX_ERROR), errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid command-line argument for server process: %s", argv[optind]), errmsg("invalid command-line argument for server process: %s", argv[optind]),
errhint("Try \"%s --help\" for more information.", progname))); errhint("Try \"%s --help\" for more information.", progname));
else else
ereport(FATAL, ereport(FATAL,
(errcode(ERRCODE_SYNTAX_ERROR), errcode(ERRCODE_SYNTAX_ERROR),
errmsg("%s: invalid command-line argument: %s", errmsg("%s: invalid command-line argument: %s",
progname, argv[optind]), progname, argv[optind]),
errhint("Try \"%s --help\" for more information.", progname))); errhint("Try \"%s --help\" for more information.", progname));
} }
/* /*
......
...@@ -1116,16 +1116,6 @@ errcontext_msg(const char *fmt,...) ...@@ -1116,16 +1116,6 @@ errcontext_msg(const char *fmt,...)
* translate it. Instead, each errcontext_msg() call should be preceded by * translate it. Instead, each errcontext_msg() call should be preceded by
* a set_errcontext_domain() call to specify the domain. This is usually * a set_errcontext_domain() call to specify the domain. This is usually
* done transparently by the errcontext() macro. * done transparently by the errcontext() macro.
*
* Although errcontext is primarily meant for use at call sites distant from
* the original ereport call, there are a few places that invoke errcontext
* within ereport. The expansion of errcontext as a comma expression calling
* set_errcontext_domain then errcontext_msg is problematic in this case,
* because the intended comma expression becomes two arguments to errfinish,
* which the compiler is at liberty to evaluate in either order. But in
* such a case, the set_errcontext_domain calls must be selecting the same
* TEXTDOMAIN value that the errstart call did, so order does not matter
* so long as errstart initializes context_domain along with domain.
*/ */
int int
set_errcontext_domain(const char *domain) set_errcontext_domain(const char *domain)
......
...@@ -91,9 +91,9 @@ ...@@ -91,9 +91,9 @@
/*---------- /*----------
* New-style error reporting API: to be used in this way: * New-style error reporting API: to be used in this way:
* ereport(ERROR, * ereport(ERROR,
* (errcode(ERRCODE_UNDEFINED_CURSOR), * errcode(ERRCODE_UNDEFINED_CURSOR),
* errmsg("portal \"%s\" not found", stmt->portalname), * errmsg("portal \"%s\" not found", stmt->portalname),
* ... other errxxx() fields as needed ...)); * ... other errxxx() fields as needed ...);
* *
* The error level is required, and so is a primary error message (errmsg * The error level is required, and so is a primary error message (errmsg
* or errmsg_internal). All else is optional. errcode() defaults to * or errmsg_internal). All else is optional. errcode() defaults to
...@@ -101,6 +101,9 @@ ...@@ -101,6 +101,9 @@
* if elevel is WARNING, or ERRCODE_SUCCESSFUL_COMPLETION if elevel is * if elevel is WARNING, or ERRCODE_SUCCESSFUL_COMPLETION if elevel is
* NOTICE or below. * NOTICE or below.
* *
* Before Postgres v12, extra parentheses were required around the
* list of auxiliary function calls; that's now optional.
*
* ereport_domain() allows a message domain to be specified, for modules that * ereport_domain() allows a message domain to be specified, for modules that
* wish to use a different message catalog from the backend's. To avoid having * wish to use a different message catalog from the backend's. To avoid having
* one copy of the default text domain per .o file, we define it as NULL here * one copy of the default text domain per .o file, we define it as NULL here
...@@ -118,28 +121,28 @@ ...@@ -118,28 +121,28 @@
*---------- *----------
*/ */
#ifdef HAVE__BUILTIN_CONSTANT_P #ifdef HAVE__BUILTIN_CONSTANT_P
#define ereport_domain(elevel, domain, rest) \ #define ereport_domain(elevel, domain, ...) \
do { \ do { \
pg_prevent_errno_in_scope(); \ pg_prevent_errno_in_scope(); \
if (errstart(elevel, __FILE__, __LINE__, PG_FUNCNAME_MACRO, domain)) \ if (errstart(elevel, __FILE__, __LINE__, PG_FUNCNAME_MACRO, domain)) \
errfinish rest; \ __VA_ARGS__, errfinish(0); \
if (__builtin_constant_p(elevel) && (elevel) >= ERROR) \ if (__builtin_constant_p(elevel) && (elevel) >= ERROR) \
pg_unreachable(); \ pg_unreachable(); \
} while(0) } while(0)
#else /* !HAVE__BUILTIN_CONSTANT_P */ #else /* !HAVE__BUILTIN_CONSTANT_P */
#define ereport_domain(elevel, domain, rest) \ #define ereport_domain(elevel, domain, ...) \
do { \ do { \
const int elevel_ = (elevel); \ const int elevel_ = (elevel); \
pg_prevent_errno_in_scope(); \ pg_prevent_errno_in_scope(); \
if (errstart(elevel_, __FILE__, __LINE__, PG_FUNCNAME_MACRO, domain)) \ if (errstart(elevel_, __FILE__, __LINE__, PG_FUNCNAME_MACRO, domain)) \
errfinish rest; \ __VA_ARGS__, errfinish(0); \
if (elevel_ >= ERROR) \ if (elevel_ >= ERROR) \
pg_unreachable(); \ pg_unreachable(); \
} while(0) } while(0)
#endif /* HAVE__BUILTIN_CONSTANT_P */ #endif /* HAVE__BUILTIN_CONSTANT_P */
#define ereport(elevel, rest) \ #define ereport(elevel, ...) \
ereport_domain(elevel, TEXTDOMAIN, rest) ereport_domain(elevel, TEXTDOMAIN, __VA_ARGS__)
#define TEXTDOMAIN NULL #define TEXTDOMAIN NULL
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment