Skip to content

Commit d9f7f5d

Browse files
committed
Create infrastructure for "soft" error reporting.
Postgres' standard mechanism for reporting errors (ereport() or elog()) is used for all sorts of error conditions. This means that throwing an exception via ereport(ERROR) requires an expensive transaction or subtransaction abort and cleanup, since the exception catcher dare not make many assumptions about what has gone wrong. There are situations where we would rather have a lighter-weight mechanism for dealing with errors that are known to be safe to recover from without a full transaction cleanup. This commit creates infrastructure to let us adapt existing error-reporting code for that purpose. See the included documentation changes for details. Follow-on commits will provide test code and usage examples. The near-term plan is to convert most if not all datatype input functions to report invalid input "softly". This will enable implementing some SQL/JSON features cleanly and without the cost of subtransactions, and it will also allow creating COPY options to deal with bad input without cancelling the whole COPY. This patch is mostly by me, but it owes very substantial debt to earlier work by Nikita Glukhov, Andrew Dunstan, and Amul Sul. Thanks also to Andres Freund for review. Discussion: https://postgr.es/m/3bbbb0df-7382-bf87-9737-340ba096e034@postgrespro.ru
1 parent beecbe8 commit d9f7f5d

File tree

11 files changed

+397
-0
lines changed

11 files changed

+397
-0
lines changed

doc/src/sgml/ref/create_type.sgml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable>
900900
function is written in C.
901901
</para>
902902

903+
<para>
904+
In <productname>PostgreSQL</productname> version 16 and later,
905+
it is desirable for base types' input functions to
906+
return <quote>soft</quote> errors using the
907+
new <function>errsave()</function>/<function>ereturn()</function>
908+
mechanism, rather than throwing <function>ereport()</function>
909+
exceptions as in previous versions.
910+
See <filename>src/backend/utils/fmgr/README</filename> for more
911+
information.
912+
</para>
913+
903914
</refsect1>
904915

905916
<refsect1>

src/backend/nodes/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ node_headers = \
5656
nodes/bitmapset.h \
5757
nodes/extensible.h \
5858
nodes/lockoptions.h \
59+
nodes/miscnodes.h \
5960
nodes/replnodes.h \
6061
nodes/supportnodes.h \
6162
nodes/value.h \

src/backend/nodes/gen_node_support.pl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ sub elem
6868
nodes/bitmapset.h
6969
nodes/extensible.h
7070
nodes/lockoptions.h
71+
nodes/miscnodes.h
7172
nodes/replnodes.h
7273
nodes/supportnodes.h
7374
nodes/value.h
@@ -89,6 +90,7 @@ sub elem
8990
executor/tuptable.h
9091
foreign/fdwapi.h
9192
nodes/lockoptions.h
93+
nodes/miscnodes.h
9294
nodes/replnodes.h
9395
nodes/supportnodes.h
9496
);

src/backend/utils/error/elog.c

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
#include "libpq/libpq.h"
7272
#include "libpq/pqformat.h"
7373
#include "mb/pg_wchar.h"
74+
#include "nodes/miscnodes.h"
7475
#include "miscadmin.h"
7576
#include "pgstat.h"
7677
#include "postmaster/bgworker.h"
@@ -611,6 +612,128 @@ errfinish(const char *filename, int lineno, const char *funcname)
611612
CHECK_FOR_INTERRUPTS();
612613
}
613614

615+
616+
/*
617+
* errsave_start --- begin a "soft" error-reporting cycle
618+
*
619+
* If "context" isn't an ErrorSaveContext node, this behaves as
620+
* errstart(ERROR, domain), and the errsave() macro ends up acting
621+
* exactly like ereport(ERROR, ...).
622+
*
623+
* If "context" is an ErrorSaveContext node, but the node creator only wants
624+
* notification of the fact of a soft error without any details, we just set
625+
* the error_occurred flag in the ErrorSaveContext node and return false,
626+
* which will cause us to skip the remaining error processing steps.
627+
*
628+
* Otherwise, create and initialize error stack entry and return true.
629+
* Subsequently, errmsg() and perhaps other routines will be called to further
630+
* populate the stack entry. Finally, errsave_finish() will be called to
631+
* tidy up.
632+
*/
633+
bool
634+
errsave_start(struct Node *context, const char *domain)
635+
{
636+
ErrorSaveContext *escontext;
637+
ErrorData *edata;
638+
639+
/*
640+
* Do we have a context for soft error reporting? If not, just punt to
641+
* errstart().
642+
*/
643+
if (context == NULL || !IsA(context, ErrorSaveContext))
644+
return errstart(ERROR, domain);
645+
646+
/* Report that a soft error was detected */
647+
escontext = (ErrorSaveContext *) context;
648+
escontext->error_occurred = true;
649+
650+
/* Nothing else to do if caller wants no further details */
651+
if (!escontext->details_wanted)
652+
return false;
653+
654+
/*
655+
* Okay, crank up a stack entry to store the info in.
656+
*/
657+
658+
recursion_depth++;
659+
660+
/* Initialize data for this error frame */
661+
edata = get_error_stack_entry();
662+
edata->elevel = LOG; /* signal all is well to errsave_finish */
663+
set_stack_entry_domain(edata, domain);
664+
/* Select default errcode based on the assumed elevel of ERROR */
665+
edata->sqlerrcode = ERRCODE_INTERNAL_ERROR;
666+
667+
/*
668+
* Any allocations for this error state level should go into the caller's
669+
* context. We don't need to pollute ErrorContext, or even require it to
670+
* exist, in this code path.
671+
*/
672+
edata->assoc_context = CurrentMemoryContext;
673+
674+
recursion_depth--;
675+
return true;
676+
}
677+
678+
/*
679+
* errsave_finish --- end a "soft" error-reporting cycle
680+
*
681+
* If errsave_start() decided this was a regular error, behave as
682+
* errfinish(). Otherwise, package up the error details and save
683+
* them in the ErrorSaveContext node.
684+
*/
685+
void
686+
errsave_finish(struct Node *context, const char *filename, int lineno,
687+
const char *funcname)
688+
{
689+
ErrorSaveContext *escontext = (ErrorSaveContext *) context;
690+
ErrorData *edata = &errordata[errordata_stack_depth];
691+
692+
/* verify stack depth before accessing *edata */
693+
CHECK_STACK_DEPTH();
694+
695+
/*
696+
* If errsave_start punted to errstart, then elevel will be ERROR or
697+
* perhaps even PANIC. Punt likewise to errfinish.
698+
*/
699+
if (edata->elevel >= ERROR)
700+
{
701+
errfinish(filename, lineno, funcname);
702+
pg_unreachable();
703+
}
704+
705+
/*
706+
* Else, we should package up the stack entry contents and deliver them to
707+
* the caller.
708+
*/
709+
recursion_depth++;
710+
711+
/* Save the last few bits of error state into the stack entry */
712+
set_stack_entry_location(edata, filename, lineno, funcname);
713+
714+
/* Replace the LOG value that errsave_start inserted */
715+
edata->elevel = ERROR;
716+
717+
/*
718+
* We skip calling backtrace and context functions, which are more likely
719+
* to cause trouble than provide useful context; they might act on the
720+
* assumption that a transaction abort is about to occur.
721+
*/
722+
723+
/*
724+
* Make a copy of the error info for the caller. All the subsidiary
725+
* strings are already in the caller's context, so it's sufficient to
726+
* flat-copy the stack entry.
727+
*/
728+
escontext->error_data = palloc_object(ErrorData);
729+
memcpy(escontext->error_data, edata, sizeof(ErrorData));
730+
731+
/* Exit error-handling context */
732+
errordata_stack_depth--;
733+
recursion_depth--;
734+
}
735+
736+
614737
/*
615738
* get_error_stack_entry --- allocate and initialize a new stack entry
616739
*

src/backend/utils/fmgr/README

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,78 @@ See windowapi.h for more information.
267267
information about the context of the CALL statement, particularly
268268
whether it is within an "atomic" execution context.
269269

270+
* Some callers of datatype input functions (and in future perhaps
271+
other classes of functions) pass an instance of ErrorSaveContext.
272+
This indicates that the caller wishes to handle "soft" errors without
273+
a transaction-terminating exception being thrown: instead, the callee
274+
should store information about the error cause in the ErrorSaveContext
275+
struct and return a dummy result value. Further details appear in
276+
"Handling Soft Errors" below.
277+
278+
279+
Handling Soft Errors
280+
--------------------
281+
282+
Postgres' standard mechanism for reporting errors (ereport() or elog())
283+
is used for all sorts of error conditions. This means that throwing
284+
an exception via ereport(ERROR) requires an expensive transaction or
285+
subtransaction abort and cleanup, since the exception catcher dare not
286+
make many assumptions about what has gone wrong. There are situations
287+
where we would rather have a lighter-weight mechanism for dealing
288+
with errors that are known to be safe to recover from without a full
289+
transaction cleanup. SQL-callable functions can support this need
290+
using the ErrorSaveContext context mechanism.
291+
292+
To report a "soft" error, a SQL-callable function should call
293+
errsave(fcinfo->context, ...)
294+
where it would previously have done
295+
ereport(ERROR, ...)
296+
If the passed "context" is NULL or is not an ErrorSaveContext node,
297+
then errsave behaves precisely as ereport(ERROR): the exception is
298+
thrown via longjmp, so that control does not return. If "context"
299+
is an ErrorSaveContext node, then the error information included in
300+
errsave's subsidiary reporting calls is stored into the context node
301+
and control returns from errsave normally. The function should then
302+
return a dummy value to its caller. (SQL NULL is recommendable as
303+
the dummy value; but anything will do, since the caller is expected
304+
to ignore the function's return value once it sees that an error has
305+
been reported in the ErrorSaveContext node.)
306+
307+
If there is nothing to do except return after calling errsave(),
308+
you can save a line or two by writing
309+
ereturn(fcinfo->context, dummy_value, ...)
310+
to perform errsave() and then "return dummy_value".
311+
312+
An error reported "softly" must be safe, in the sense that there is
313+
no question about our ability to continue normal processing of the
314+
transaction. Error conditions that should NOT be handled this way
315+
include out-of-memory, unexpected internal errors, or anything that
316+
cannot easily be cleaned up after. Such cases should still be thrown
317+
with ereport, as they have been in the past.
318+
319+
Considering datatype input functions as examples, typical "soft" error
320+
conditions include input syntax errors and out-of-range values. An
321+
input function typically detects such cases with simple if-tests and
322+
can easily change the ensuing ereport call to an errsave or ereturn.
323+
Because of this restriction, it's typically not necessary to pass
324+
the ErrorSaveContext pointer down very far, as errors reported by
325+
low-level functions are typically reasonable to consider internal.
326+
(Another way to frame the distinction is that input functions should
327+
report all invalid-input conditions softly, but internal problems are
328+
hard errors.)
329+
330+
Because no transaction cleanup will occur, a function that is exiting
331+
after errsave() returns will bear responsibility for resource cleanup.
332+
It is not necessary to be concerned about small leakages of palloc'd
333+
memory, since the caller should be running the function in a short-lived
334+
memory context. However, resources such as locks, open files, or buffer
335+
pins must be closed out cleanly, as they would be in the non-error code
336+
path.
337+
338+
Conventions for callers that use the ErrorSaveContext mechanism
339+
to trap errors are discussed with the declaration of that struct,
340+
in nodes/miscnodes.h.
341+
270342

271343
Functions Accepting or Returning Sets
272344
-------------------------------------

src/backend/utils/fmgr/fmgr.c

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "lib/stringinfo.h"
2424
#include "miscadmin.h"
2525
#include "nodes/makefuncs.h"
26+
#include "nodes/miscnodes.h"
2627
#include "nodes/nodeFuncs.h"
2728
#include "pgstat.h"
2829
#include "utils/acl.h"
@@ -1549,6 +1550,70 @@ InputFunctionCall(FmgrInfo *flinfo, char *str, Oid typioparam, int32 typmod)
15491550
return result;
15501551
}
15511552

1553+
/*
1554+
* Call a previously-looked-up datatype input function, with non-exception
1555+
* handling of "soft" errors.
1556+
*
1557+
* This is basically like InputFunctionCall, but the converted Datum is
1558+
* returned into *result while the function result is true for success or
1559+
* false for failure. Also, the caller may pass an ErrorSaveContext node.
1560+
* (We declare that as "fmNodePtr" to avoid including nodes.h in fmgr.h.)
1561+
*
1562+
* If escontext points to an ErrorSaveContext, any "soft" errors detected by
1563+
* the input function will be reported by filling the escontext struct and
1564+
* returning false. (The caller can choose to test SOFT_ERROR_OCCURRED(),
1565+
* but checking the function result instead is usually cheaper.)
1566+
*
1567+
* If escontext does not point to an ErrorSaveContext, errors are reported
1568+
* via ereport(ERROR), so that there is no functional difference from
1569+
* InputFunctionCall; the result will always be true if control returns.
1570+
*/
1571+
bool
1572+
InputFunctionCallSafe(FmgrInfo *flinfo, char *str,
1573+
Oid typioparam, int32 typmod,
1574+
fmNodePtr escontext,
1575+
Datum *result)
1576+
{
1577+
LOCAL_FCINFO(fcinfo, 3);
1578+
1579+
if (str == NULL && flinfo->fn_strict)
1580+
{
1581+
*result = (Datum) 0; /* just return null result */
1582+
return true;
1583+
}
1584+
1585+
InitFunctionCallInfoData(*fcinfo, flinfo, 3, InvalidOid, escontext, NULL);
1586+
1587+
fcinfo->args[0].value = CStringGetDatum(str);
1588+
fcinfo->args[0].isnull = false;
1589+
fcinfo->args[1].value = ObjectIdGetDatum(typioparam);
1590+
fcinfo->args[1].isnull = false;
1591+
fcinfo->args[2].value = Int32GetDatum(typmod);
1592+
fcinfo->args[2].isnull = false;
1593+
1594+
*result = FunctionCallInvoke(fcinfo);
1595+
1596+
/* Result value is garbage, and could be null, if an error was reported */
1597+
if (SOFT_ERROR_OCCURRED(escontext))
1598+
return false;
1599+
1600+
/* Otherwise, should get null result if and only if str is NULL */
1601+
if (str == NULL)
1602+
{
1603+
if (!fcinfo->isnull)
1604+
elog(ERROR, "input function %u returned non-NULL",
1605+
flinfo->fn_oid);
1606+
}
1607+
else
1608+
{
1609+
if (fcinfo->isnull)
1610+
elog(ERROR, "input function %u returned NULL",
1611+
flinfo->fn_oid);
1612+
}
1613+
1614+
return true;
1615+
}
1616+
15521617
/*
15531618
* Call a previously-looked-up datatype output function.
15541619
*

src/include/fmgr.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,10 @@ extern Datum OidFunctionCall9Coll(Oid functionId, Oid collation,
700700
/* Special cases for convenient invocation of datatype I/O functions. */
701701
extern Datum InputFunctionCall(FmgrInfo *flinfo, char *str,
702702
Oid typioparam, int32 typmod);
703+
extern bool InputFunctionCallSafe(FmgrInfo *flinfo, char *str,
704+
Oid typioparam, int32 typmod,
705+
fmNodePtr escontext,
706+
Datum *result);
703707
extern Datum OidInputFunctionCall(Oid functionId, char *str,
704708
Oid typioparam, int32 typmod);
705709
extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val);

src/include/nodes/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ node_support_input_i = [
1616
'nodes/bitmapset.h',
1717
'nodes/extensible.h',
1818
'nodes/lockoptions.h',
19+
'nodes/miscnodes.h',
1920
'nodes/replnodes.h',
2021
'nodes/supportnodes.h',
2122
'nodes/value.h',

0 commit comments

Comments
 (0)