Skip to content

Commit

Permalink
Add infrastucture for "soft" error handle
Browse files Browse the repository at this point in the history
  • Loading branch information
robozmey committed Dec 11, 2024
1 parent 1d41230 commit 4af3669
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 0 deletions.
32 changes: 32 additions & 0 deletions src/backend/utils/error/elog.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
#include "libpq/pqformat.h"
#include "libpq/pqsignal.h"
#include "mb/pg_wchar.h"
#include "nodes/miscnodes.h"
#include "miscadmin.h"
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
Expand Down Expand Up @@ -830,6 +831,37 @@ errfinish_and_return(int dummy __attribute__((unused)),...)
return edata_copy;
}

/*
* 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, 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.
*/
bool
errsave_start(struct Node *context,
const char *filename, int lineno,
const char *funcname, const char *domain)
{
ErrorSaveContext *escontext;

/*
* Do we have a context for soft error reporting? If not, just punt to
* errstart().
*/
if (context == NULL || !IsA(context, ErrorSaveContext))
return errstart(ERROR, filename, lineno, funcname, domain);

/* Report that a soft error was detected */
escontext = (ErrorSaveContext *) context;
escontext->error_occurred = true;

return false;
}

/*
* errcode --- add SQLSTATE error code to the current error
Expand Down
113 changes: 113 additions & 0 deletions src/backend/utils/fmgr/fmgr.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "lib/stringinfo.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "nodes/miscnodes.h"
#include "pgstat.h"
#include "utils/acl.h"
#include "utils/builtins.h"
Expand Down Expand Up @@ -1887,6 +1888,55 @@ OidFunctionCall9Coll(Oid functionId, Oid collation, Datum arg1, Datum arg2,
return result;
}

/*
* Call a previously-looked-up OidFunctionCallNColl function, with non-exception
* handling of "soft" errors.
*
* This is basically like OidFunctionCallNColl, but the converted Datum is
* returned into *result while the function result is true for success or
* false for failure. Also, the caller may pass an ErrorSaveContext node.
* (We declare that as "fmNodePtr" to avoid including nodes.h in fmgr.h.)
*
* If escontext points to an ErrorSaveContext, any "soft" errors detected by
* the input function will be reported by filling the escontext struct and
* returning (Datum) 0.
*
* If escontext does not point to an ErrorSaveContext, errors are reported
* via ereport(ERROR), so that there is no functional difference from
* OidFunctionCall3CollSafe; the result will always be true if control returns.
*/
Datum
OidFunctionCall3CollSafe(Oid functionId, Oid collation, Datum arg1, Datum arg2,
Datum arg3, fmNodePtr escontext)
{
FmgrInfo flinfo;
FunctionCallInfoData fcinfo;
Datum result;

fmgr_info(functionId, &flinfo);

InitFunctionCallInfoData(fcinfo, &flinfo, 3, collation, escontext, NULL);

fcinfo.arg[0] = arg1;
fcinfo.arg[1] = arg2;
fcinfo.arg[2] = arg3;
fcinfo.argnull[0] = false;
fcinfo.argnull[1] = false;
fcinfo.argnull[2] = false;

result = FunctionCallInvoke(&fcinfo);

/* Result value is garbage, and could be null, if an error was reported */
if (SOFT_ERROR_OCCURRED(escontext))
return (Datum) 0;

/* Check for null result, since caller is clearly not expecting one */
if (fcinfo.isnull)
elog(ERROR, "function %u returned NULL", flinfo.fn_oid);

return result;
}


/*
* Special cases for convenient invocation of datatype I/O functions.
Expand Down Expand Up @@ -1948,6 +1998,69 @@ InputFunctionCall(FmgrInfo *flinfo, char *str, Oid typioparam, int32 typmod)
return result;
}

/*
* Call a previously-looked-up datatype input function, with non-exception
* handling of "soft" errors.
*
* This is basically like InputFunctionCall, but the converted Datum is
* returned into *result while the function result is true for success or
* false for failure. Also, the caller may pass an ErrorSaveContext node.
* (We declare that as "fmNodePtr" to avoid including nodes.h in fmgr.h.)
*
* If escontext points to an ErrorSaveContext, any "soft" errors detected by
* the input function will be reported by filling the escontext struct and
* returning (Datum) 0.
*
* If escontext does not point to an ErrorSaveContext, errors are reported
* via ereport(ERROR), so that there is no functional difference from
* InputFunctionCall; the result will always be true if control returns.
*/
Datum
InputFunctionCallSafe(FmgrInfo *flinfo, char *str, Oid typioparam, int32 typmod, fmNodePtr escontext)
{
FunctionCallInfoData fcinfo;
Datum result;
bool pushed;

if (str == NULL && flinfo->fn_strict)
return (Datum) 0; /* just return null result */

pushed = SPI_push_conditional();

InitFunctionCallInfoData(fcinfo, flinfo, 3, InvalidOid, escontext, NULL);

fcinfo.arg[0] = CStringGetDatum(str);
fcinfo.arg[1] = ObjectIdGetDatum(typioparam);
fcinfo.arg[2] = Int32GetDatum(typmod);
fcinfo.argnull[0] = (str == NULL);
fcinfo.argnull[1] = false;
fcinfo.argnull[2] = false;

result = FunctionCallInvoke(&fcinfo);

/* Result value is garbage, and could be null, if an error was reported */
if (SOFT_ERROR_OCCURRED(escontext))
return (Datum) 0;

/* Should get null result if and only if str is NULL */
if (str == NULL)
{
if (!fcinfo.isnull)
elog(ERROR, "input function %u returned non-NULL",
fcinfo.flinfo->fn_oid);
}
else
{
if (fcinfo.isnull)
elog(ERROR, "input function %u returned NULL",
fcinfo.flinfo->fn_oid);
}

SPI_pop_conditional(pushed);

return result;
}

/*
* Call a previously-looked-up datatype output function.
*
Expand Down
10 changes: 10 additions & 0 deletions src/include/fmgr.h
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,10 @@ extern Datum OidFunctionCall9Coll(Oid functionId, Oid collation,
Datum arg6, Datum arg7, Datum arg8,
Datum arg9);

extern Datum OidFunctionCall3CollSafe(Oid functionId, Oid collation,
Datum arg1, Datum arg2,
Datum arg3, fmNodePtr escontext);

/* These macros allow the collation argument to be omitted (with a default of
* InvalidOid, ie, no collation). They exist mostly for backwards
* compatibility of source code.
Expand Down Expand Up @@ -641,9 +645,15 @@ extern Datum OidFunctionCall9Coll(Oid functionId, Oid collation,
OidFunctionCall9Coll(functionId, InvalidOid, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)


#define OidFunctionCall3Safe(functionId, arg1, arg2, arg3, escontext) \
OidFunctionCall3CollSafe(functionId, InvalidOid, arg1, arg2, arg3, escontext)


/* Special cases for convenient invocation of datatype I/O functions. */
extern Datum InputFunctionCall(FmgrInfo *flinfo, char *str,
Oid typioparam, int32 typmod);
extern Datum InputFunctionCallSafe(FmgrInfo *flinfo, char *str,
Oid typioparam, int32 typmod, fmNodePtr escontext);
extern Datum OidInputFunctionCall(Oid functionId, char *str,
Oid typioparam, int32 typmod);
extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val);
Expand Down
46 changes: 46 additions & 0 deletions src/include/nodes/miscnodes.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*-------------------------------------------------------------------------
*
* miscnodes.h
* Definitions for hard-to-classify node types.
*
* Node types declared here are not part of parse trees, plan trees,
* or execution state trees. We only assign them NodeTag values because
* IsA() tests provide a convenient way to disambiguate what kind of
* structure is being passed through assorted APIs, such as function
* "context" pointers.
*
* src/include/nodes/miscnodes.h
*
*-------------------------------------------------------------------------
*/

#ifndef MISCNODES_H
#define MISCNODES_H
#include "nodes/nodes.h"

/*
* ErrorSaveContext -
* function call context node for handling of "soft" errors
*
* A caller wishing to trap soft errors must initialize a struct like this
* with all fields zero/NULL except for the NodeTag. Optionally, set
* details_wanted = true if more than the bare knowledge that a soft error
* occurred is required. The struct is then passed to a SQL-callable function
* via the FunctionCallInfo.context field; or below the level of SQL calls,
* it could be passed to a subroutine directly.
*
* After calling code that might report an error this way, check
* error_occurred to see if an error happened.
*/
typedef struct ErrorSaveContext
{
NodeTag type;
bool error_occurred; /* set to true if we detect a soft error */
} ErrorSaveContext;

/* Often-useful macro for checking if a soft error was reported */
#define SOFT_ERROR_OCCURRED(escontext) \
((escontext) != NULL && IsA(escontext, ErrorSaveContext) && \
((ErrorSaveContext *) (escontext))->error_occurred)

#endif
2 changes: 2 additions & 0 deletions src/include/nodes/nodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,8 @@ typedef enum NodeTag
T_GpPolicy, /* in catalog/gp_policy.h */
T_RetrieveStmt,

T_ErrorSaveContext,

} NodeTag;

/*
Expand Down
54 changes: 54 additions & 0 deletions src/include/utils/elog.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
#include <sys/time.h>
#include <setjmp.h>

/* We cannot include nodes.h yet, so forward-declare struct Node */
struct Node;

/* Error level codes */
#define DEBUG5 10 /* Debugging messages, in categories of
* decreasing detail. */
Expand Down Expand Up @@ -204,6 +207,57 @@ extern bool errstart(int elevel, const char *filename, int lineno,
const char *funcname, const char *domain);
extern void errfinish(int dummy,...);

/*----------
* Support for reporting "soft" errors that don't require a full transaction
* abort to clean up. This is to be used in this way:
* errsave(context,
* errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
* errmsg("invalid input syntax for type %s: \"%s\"",
* "boolean", in_str),
* ... other errxxx() fields as needed ...);
*
* "context" is a node pointer or NULL, and the remaining auxiliary calls
* provide the same error details as in ereport(). If context is not a
* pointer to an ErrorSaveContext node, then errsave(context, ...)
* behaves identically to ereport(ERROR, ...). If context is a pointer
* to an ErrorSaveContext node, then the information provided by the
* auxiliary calls is stored in the context node and control returns
* normally. The caller of errsave() must then do any required cleanup
* and return control back to its caller. That caller must check the
* ErrorSaveContext node to see whether an error occurred before
* it can trust the function's result to be meaningful.
*
* errsave_domain() allows a message domain to be specified; it is
* precisely analogous to ereport_domain().
*----------
*/
#define errsave_domain(context, domain, rest) \
do { \
struct Node *context_ = (context); \
if (errsave_start(context_, __FILE__, __LINE__, __func__, domain)) \
errfinish rest; \
} while(0)
#define errsave(context, rest) \
errsave_domain(context, TEXTDOMAIN, rest)

/*
* "ereturn(context, dummy_value, ...);" is exactly the same as
* "errsave(context, ...); return dummy_value;". This saves a bit
* of typing in the common case where a function has no cleanup
* actions to take after reporting a soft error. "dummy_value"
* can be empty if the function returns void.
*/
#define ereturn_domain(context, dummy_value, domain, rest) \
do { \
errsave_domain(context, domain, rest); \
return dummy_value; \
} while(0)
#define ereturn(context, dummy_value, rest) \
ereturn_domain(context, dummy_value, TEXTDOMAIN, rest)

extern bool errsave_start(struct Node* context, const char *filename, int lineno,
const char *funcname, const char *domain);

extern int errcode(int sqlerrcode);

extern int errcode_for_file_access(void);
Expand Down
1 change: 1 addition & 0 deletions src/tools/pgindent/typedefs.list
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@ EquivalenceClass
EquivalenceMember
ErrorContextCallback
ErrorData
ErrorSaveContext
EventTriggerCacheEntry
EventTriggerCacheItem
EventTriggerCacheStateType
Expand Down

0 comments on commit 4af3669

Please sign in to comment.