Create infrastructure for "soft" error reporting.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 9 Dec 2022 14:58:38 +0000 (09:58 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 9 Dec 2022 14:58:38 +0000 (09:58 -0500)
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

doc/src/sgml/ref/create_type.sgml
src/backend/nodes/Makefile
src/backend/nodes/gen_node_support.pl
src/backend/utils/error/elog.c
src/backend/utils/fmgr/README
src/backend/utils/fmgr/fmgr.c
src/include/fmgr.h
src/include/nodes/meson.build
src/include/nodes/miscnodes.h [new file with mode: 0644]
src/include/utils/elog.h
src/tools/pgindent/typedefs.list

index 693423e5243508222907eb1e4ac290cd99aea512..994dfc65268bd1585c10da503eb64c699baa7a20 100644 (file)
@@ -900,6 +900,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable>
    function is written in C.
   </para>
 
+  <para>
+   In <productname>PostgreSQL</productname> version 16 and later,
+   it is desirable for base types' input functions to
+   return <quote>soft</quote> errors using the
+   new <function>errsave()</function>/<function>ereturn()</function>
+   mechanism, rather than throwing <function>ereport()</function>
+   exceptions as in previous versions.
+   See <filename>src/backend/utils/fmgr/README</filename> for more
+   information.
+  </para>
+
  </refsect1>
 
  <refsect1>
index 4368c30fdbb03e55b86372afb4093f3673ab4eea..7c594be5837e3a526dc20985be871c0e1685c6bc 100644 (file)
@@ -56,6 +56,7 @@ node_headers = \
    nodes/bitmapset.h \
    nodes/extensible.h \
    nodes/lockoptions.h \
+   nodes/miscnodes.h \
    nodes/replnodes.h \
    nodes/supportnodes.h \
    nodes/value.h \
index 7212bc486f3da17212a76fe114f03c087bc48467..08992dfd4761b88a1db0d36d2728c8e38f1f8c38 100644 (file)
@@ -68,6 +68,7 @@ my @all_input_files = qw(
   nodes/bitmapset.h
   nodes/extensible.h
   nodes/lockoptions.h
+  nodes/miscnodes.h
   nodes/replnodes.h
   nodes/supportnodes.h
   nodes/value.h
@@ -89,6 +90,7 @@ my @nodetag_only_files = qw(
   executor/tuptable.h
   foreign/fdwapi.h
   nodes/lockoptions.h
+  nodes/miscnodes.h
   nodes/replnodes.h
   nodes/supportnodes.h
 );
index f5cd1b74937443fe286e0cb20e7c2d69e41aa8ac..eb489ea3a70746c67c1734f2a1bb1ac1e0a6e770 100644 (file)
@@ -71,6 +71,7 @@
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
+#include "nodes/miscnodes.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
@@ -611,6 +612,128 @@ errfinish(const char *filename, int lineno, const char *funcname)
    CHECK_FOR_INTERRUPTS();
 }
 
+
+/*
+ * errsave_start --- begin a "soft" error-reporting cycle
+ *
+ * If "context" isn't an ErrorSaveContext node, this behaves as
+ * errstart(ERROR, domain), and the errsave() macro ends up acting
+ * exactly like ereport(ERROR, ...).
+ *
+ * If "context" is an ErrorSaveContext node, but the node creator only wants
+ * notification of the fact of a soft error without any details, we just set
+ * the error_occurred flag in the ErrorSaveContext node and return false,
+ * which will cause us to skip the remaining error processing steps.
+ *
+ * Otherwise, create and initialize error stack entry and return true.
+ * Subsequently, errmsg() and perhaps other routines will be called to further
+ * populate the stack entry.  Finally, errsave_finish() will be called to
+ * tidy up.
+ */
+bool
+errsave_start(struct Node *context, const char *domain)
+{
+   ErrorSaveContext *escontext;
+   ErrorData  *edata;
+
+   /*
+    * Do we have a context for soft error reporting?  If not, just punt to
+    * errstart().
+    */
+   if (context == NULL || !IsA(context, ErrorSaveContext))
+       return errstart(ERROR, domain);
+
+   /* Report that a soft error was detected */
+   escontext = (ErrorSaveContext *) context;
+   escontext->error_occurred = true;
+
+   /* Nothing else to do if caller wants no further details */
+   if (!escontext->details_wanted)
+       return false;
+
+   /*
+    * Okay, crank up a stack entry to store the info in.
+    */
+
+   recursion_depth++;
+
+   /* Initialize data for this error frame */
+   edata = get_error_stack_entry();
+   edata->elevel = LOG;        /* signal all is well to errsave_finish */
+   set_stack_entry_domain(edata, domain);
+   /* Select default errcode based on the assumed elevel of ERROR */
+   edata->sqlerrcode = ERRCODE_INTERNAL_ERROR;
+
+   /*
+    * Any allocations for this error state level should go into the caller's
+    * context.  We don't need to pollute ErrorContext, or even require it to
+    * exist, in this code path.
+    */
+   edata->assoc_context = CurrentMemoryContext;
+
+   recursion_depth--;
+   return true;
+}
+
+/*
+ * errsave_finish --- end a "soft" error-reporting cycle
+ *
+ * If errsave_start() decided this was a regular error, behave as
+ * errfinish().  Otherwise, package up the error details and save
+ * them in the ErrorSaveContext node.
+ */
+void
+errsave_finish(struct Node *context, const char *filename, int lineno,
+              const char *funcname)
+{
+   ErrorSaveContext *escontext = (ErrorSaveContext *) context;
+   ErrorData  *edata = &errordata[errordata_stack_depth];
+
+   /* verify stack depth before accessing *edata */
+   CHECK_STACK_DEPTH();
+
+   /*
+    * If errsave_start punted to errstart, then elevel will be ERROR or
+    * perhaps even PANIC.  Punt likewise to errfinish.
+    */
+   if (edata->elevel >= ERROR)
+   {
+       errfinish(filename, lineno, funcname);
+       pg_unreachable();
+   }
+
+   /*
+    * Else, we should package up the stack entry contents and deliver them to
+    * the caller.
+    */
+   recursion_depth++;
+
+   /* Save the last few bits of error state into the stack entry */
+   set_stack_entry_location(edata, filename, lineno, funcname);
+
+   /* Replace the LOG value that errsave_start inserted */
+   edata->elevel = ERROR;
+
+   /*
+    * We skip calling backtrace and context functions, which are more likely
+    * to cause trouble than provide useful context; they might act on the
+    * assumption that a transaction abort is about to occur.
+    */
+
+   /*
+    * Make a copy of the error info for the caller.  All the subsidiary
+    * strings are already in the caller's context, so it's sufficient to
+    * flat-copy the stack entry.
+    */
+   escontext->error_data = palloc_object(ErrorData);
+   memcpy(escontext->error_data, edata, sizeof(ErrorData));
+
+   /* Exit error-handling context */
+   errordata_stack_depth--;
+   recursion_depth--;
+}
+
+
 /*
  * get_error_stack_entry --- allocate and initialize a new stack entry
  *
index 49845f67accb72abf93ecbffce35d366a5efc5b1..9958d38992bebd59428768625dfdbfb028a72135 100644 (file)
@@ -267,6 +267,78 @@ See windowapi.h for more information.
 information about the context of the CALL statement, particularly
 whether it is within an "atomic" execution context.
 
+* Some callers of datatype input functions (and in future perhaps
+other classes of functions) pass an instance of ErrorSaveContext.
+This indicates that the caller wishes to handle "soft" errors without
+a transaction-terminating exception being thrown: instead, the callee
+should store information about the error cause in the ErrorSaveContext
+struct and return a dummy result value.  Further details appear in
+"Handling Soft Errors" below.
+
+
+Handling Soft Errors
+--------------------
+
+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.  SQL-callable functions can support this need
+using the ErrorSaveContext context mechanism.
+
+To report a "soft" error, a SQL-callable function should call
+   errsave(fcinfo->context, ...)
+where it would previously have done
+   ereport(ERROR, ...)
+If the passed "context" is NULL or is not an ErrorSaveContext node,
+then errsave behaves precisely as ereport(ERROR): the exception is
+thrown via longjmp, so that control does not return.  If "context"
+is an ErrorSaveContext node, then the error information included in
+errsave's subsidiary reporting calls is stored into the context node
+and control returns from errsave normally.  The function should then
+return a dummy value to its caller.  (SQL NULL is recommendable as
+the dummy value; but anything will do, since the caller is expected
+to ignore the function's return value once it sees that an error has
+been reported in the ErrorSaveContext node.)
+
+If there is nothing to do except return after calling errsave(),
+you can save a line or two by writing
+   ereturn(fcinfo->context, dummy_value, ...)
+to perform errsave() and then "return dummy_value".
+
+An error reported "softly" must be safe, in the sense that there is
+no question about our ability to continue normal processing of the
+transaction.  Error conditions that should NOT be handled this way
+include out-of-memory, unexpected internal errors, or anything that
+cannot easily be cleaned up after.  Such cases should still be thrown
+with ereport, as they have been in the past.
+
+Considering datatype input functions as examples, typical "soft" error
+conditions include input syntax errors and out-of-range values.  An
+input function typically detects such cases with simple if-tests and
+can easily change the ensuing ereport call to an errsave or ereturn.
+Because of this restriction, it's typically not necessary to pass
+the ErrorSaveContext pointer down very far, as errors reported by
+low-level functions are typically reasonable to consider internal.
+(Another way to frame the distinction is that input functions should
+report all invalid-input conditions softly, but internal problems are
+hard errors.)
+
+Because no transaction cleanup will occur, a function that is exiting
+after errsave() returns will bear responsibility for resource cleanup.
+It is not necessary to be concerned about small leakages of palloc'd
+memory, since the caller should be running the function in a short-lived
+memory context.  However, resources such as locks, open files, or buffer
+pins must be closed out cleanly, as they would be in the non-error code
+path.
+
+Conventions for callers that use the ErrorSaveContext mechanism
+to trap errors are discussed with the declaration of that struct,
+in nodes/miscnodes.h.
+
 
 Functions Accepting or Returning Sets
 -------------------------------------
index cd0daa7e166a4339825d744e80ede64b3a260028..0d37f69298f731d7963aedb455a2bbca33043f2e 100644 (file)
@@ -23,6 +23,7 @@
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/miscnodes.h"
 #include "nodes/nodeFuncs.h"
 #include "pgstat.h"
 #include "utils/acl.h"