Re: Error-safe user functions - Mailing list pgsql-hackers
From | Andres Freund |
---|---|
Subject | Re: Error-safe user functions |
Date | |
Msg-id | 20221207233518.sudtaxn6omtvpzvr@awork3.anarazel.de Whole thread Raw |
In response to | Re: Error-safe user functions (Tom Lane <tgl@sss.pgh.pa.us>) |
Responses |
Re: Error-safe user functions
Re: Error-safe user functions |
List | pgsql-hackers |
Hi, On 2022-12-07 17:32:21 -0500, Tom Lane wrote: > I already pushed the 0000 elog-refactoring patch, since that seemed > uncontroversial. 0001 attached covers the same territory as before, > but I regrouped the rest so that 0002 installs the new test support > functions, then 0003 adds both the per-datatype changes and > corresponding test cases for bool, int4, arrays, and records. > The idea here is that 0003 can be pointed to as a sample of what > has to be done to datatype input functions, while the preceding > patches can be cited as relevant documentation. (I've not decided > whether to squash 0001 and 0002 together or commit them separately. I think they make sense as is. > Does it make sense to break 0003 into 4 separate commits, or is > that overkill?) I think it'd be fine either way. > + * If "context" is an ErrorSaveContext node, but the node creator only wants > + * notification of the fact of a soft error without any details, 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(NodePtr context, const char *domain) I wonder if there are potential use-cases for levels other than ERROR. I can potentially see us wanting to defer some FATALs, e.g. when they occur in process exit hooks. > +{ > + 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(); For a moment I was worried that it could lead to odd behaviour that we don't do get_error_stack_entry() when !details_wanted, due to not raising an error we'd otherwise raise. But that's a should-never-be-reached case, so ... > +/* > + * 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(NodePtr 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(); > + } It seems somewhat ugly transport this knowledge via edata->elevel, but it's not too bad. > +/* > + * We cannot include nodes.h yet, so make a stub reference. (This is also > + * used by fmgr.h, which doesn't want to depend on nodes.h either.) > + */ > +typedef struct Node *NodePtr; Seems like it'd be easier to just forward declare the struct, and use the non-typedef'ed name in the header than to have to deal with these interdependencies and the differing typenames. > +/*---------- > + * 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, ...) \ > + do { \ > + NodePtr context_ = (context); \ > + pg_prevent_errno_in_scope(); \ > + if (errsave_start(context_, domain)) \ > + __VA_ARGS__, errsave_finish(context_, __FILE__, __LINE__, __func__); \ > + } while(0) Perhaps worth noting here that the reason why the errsave_start/errsave_finish split exist differs a bit from the reason in ereport_domain()? "Over there" it's just about not wanting to incur overhead when the message isn't logged, but here we'll always have >= ERROR, but ->details_wanted can still lead to not wanting to incur the overhead. > /* > diff --git a/src/backend/utils/adt/rowtypes.c b/src/backend/utils/adt/rowtypes.c > index db843a0fbf..bdafcff02d 100644 > --- a/src/backend/utils/adt/rowtypes.c > +++ b/src/backend/utils/adt/rowtypes.c > @@ -77,6 +77,7 @@ record_in(PG_FUNCTION_ARGS) > char *string = PG_GETARG_CSTRING(0); > Oid tupType = PG_GETARG_OID(1); > int32 tupTypmod = PG_GETARG_INT32(2); > + Node *escontext = fcinfo->context; > HeapTupleHeader result; > TupleDesc tupdesc; > HeapTuple tuple; > @@ -100,7 +101,7 @@ record_in(PG_FUNCTION_ARGS) > * supply a valid typmod, and then we can do something useful for RECORD. > */ > if (tupType == RECORDOID && tupTypmod < 0) > - ereport(ERROR, > + ereturn(escontext, (Datum) 0, > (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), > errmsg("input of anonymous composite types is not implemented"))); > Is it ok that we throw an error in lookup_rowtype_tupdesc()? Normally those should not be reachable by users, I think? The new testing functions might reach it, but that seems fine, they're test functions. Greetings, Andres Freund
pgsql-hackers by date: