From ec48283e9f30a7ddf18ca32704e6ec62d139f7a2 Mon Sep 17 00:00:00 2001 From: Amit Langote Date: Mon, 11 Sep 2023 21:31:29 +0900 Subject: [PATCH v17 2/5] Add soft error handling to populate_record_field() An uncoming patch would like the ability to call it from the executor for some SQL/JSON expression nodes and ask to suppress any errors that may occur. This commit does two things mainly: * It modifies the various interfaces internal to jsonfuncs.c, and in some cases, some external functions to pass the ErrorSaveContext around. * Make necessary modifications to handle the cases where the processing is aborted partway through various functions that take an ErrorSaveContext when a soft error occurs. --- contrib/hstore/hstore_io.c | 3 +- contrib/hstore/hstore_op.c | 9 +- src/backend/catalog/objectaddress.c | 3 +- src/backend/executor/execExprInterp.c | 3 +- src/backend/utils/adt/array_userfuncs.c | 5 +- src/backend/utils/adt/arrayfuncs.c | 43 +++- src/backend/utils/adt/domains.c | 5 +- src/backend/utils/adt/expandedrecord.c | 12 +- src/backend/utils/adt/jsonfuncs.c | 292 +++++++++++++++++------ src/backend/utils/adt/orderedsetaggs.c | 6 +- src/backend/utils/adt/regexp.c | 2 +- src/include/utils/array.h | 7 +- src/include/utils/builtins.h | 3 +- src/pl/plperl/plperl.c | 7 +- src/pl/plpgsql/src/pl_exec.c | 5 +- src/pl/plpython/plpy_typeio.c | 5 +- src/pl/tcl/pltcl.c | 3 +- src/test/modules/test_regex/test_regex.c | 6 +- 18 files changed, 302 insertions(+), 117 deletions(-) diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c index 999ddad76d..fc145faa1b 100644 --- a/contrib/hstore/hstore_io.c +++ b/contrib/hstore/hstore_io.c @@ -1195,7 +1195,8 @@ hstore_populate_record(PG_FUNCTION_ARGS) domain_check(HeapTupleGetDatum(rettuple), false, argtype, &my_extra->domain_info, - fcinfo->flinfo->fn_mcxt); + fcinfo->flinfo->fn_mcxt, + NULL); ReleaseTupleDesc(tupdesc); diff --git a/contrib/hstore/hstore_op.c b/contrib/hstore/hstore_op.c index 0d4ec16d1e..5d53695be1 100644 --- a/contrib/hstore/hstore_op.c +++ b/contrib/hstore/hstore_op.c @@ -619,7 +619,8 @@ hstore_slice_to_array(PG_FUNCTION_ARGS) ARR_NDIM(key_array), ARR_DIMS(key_array), ARR_LBOUND(key_array), - TEXTOID, -1, false, TYPALIGN_INT); + TEXTOID, -1, false, TYPALIGN_INT, + NULL); PG_RETURN_POINTER(aout); } @@ -762,7 +763,8 @@ hstore_avals(PG_FUNCTION_ARGS) } a = construct_md_array(d, nulls, 1, &count, &lb, - TEXTOID, -1, false, TYPALIGN_INT); + TEXTOID, -1, false, TYPALIGN_INT, + NULL); PG_RETURN_POINTER(a); } @@ -814,7 +816,8 @@ hstore_to_array_internal(HStore *hs, int ndims) return construct_md_array(out_datums, out_nulls, ndims, out_size, lb, - TEXTOID, -1, false, TYPALIGN_INT); + TEXTOID, -1, false, TYPALIGN_INT, + NULL); } PG_FUNCTION_INFO_V1(hstore_to_array); diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index 715201f5a2..e8893613f2 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -6085,7 +6085,8 @@ strlist_to_textarray(List *list) lb[0] = 1; arr = construct_md_array(datums, nulls, 1, &j, - lb, TEXTOID, -1, false, TYPALIGN_INT); + lb, TEXTOID, -1, false, TYPALIGN_INT, + NULL); MemoryContextDelete(memcxt); diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 86dff69f4d..7b3e1d071d 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -2742,7 +2742,8 @@ ExecEvalArrayExpr(ExprState *state, ExprEvalStep *op) element_type, op->d.arrayexpr.elemlength, op->d.arrayexpr.elembyval, - op->d.arrayexpr.elemalign); + op->d.arrayexpr.elemalign, + NULL); } else { diff --git a/src/backend/utils/adt/array_userfuncs.c b/src/backend/utils/adt/array_userfuncs.c index 5c4fdcfba4..801c5efd87 100644 --- a/src/backend/utils/adt/array_userfuncs.c +++ b/src/backend/utils/adt/array_userfuncs.c @@ -857,7 +857,7 @@ array_agg_finalfn(PG_FUNCTION_ARGS) */ result = makeMdArrayResult(state, 1, dims, lbs, CurrentMemoryContext, - false); + false, NULL); PG_RETURN_DATUM(result); } @@ -1622,7 +1622,8 @@ array_shuffle_n(ArrayType *array, int n, bool keep_lb, rlbs[0] = 1; result = construct_md_array(elms, nuls, ndim, rdims, rlbs, - elmtyp, elmlen, elmbyval, elmalign); + elmtyp, elmlen, elmbyval, elmalign, + NULL); pfree(elms); pfree(nuls); diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 7828a6264b..02bc483ff6 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -21,6 +21,7 @@ #include "catalog/pg_type.h" #include "funcapi.h" #include "libpq/pqformat.h" +#include "nodes/miscnodes.h" #include "nodes/nodeFuncs.h" #include "nodes/supportnodes.h" #include "optimizer/optimizer.h" @@ -2321,7 +2322,8 @@ array_set_element(Datum arraydatum, return PointerGetDatum(construct_md_array(&dataValue, &isNull, nSubscripts, dim, lb, elmtype, - elmlen, elmbyval, elmalign)); + elmlen, elmbyval, elmalign, + NULL)); } if (ndim != nSubscripts) @@ -2881,7 +2883,8 @@ array_set_slice(Datum arraydatum, return PointerGetDatum(construct_md_array(dvalues, dnulls, nSubscripts, dim, lb, elmtype, - elmlen, elmbyval, elmalign)); + elmlen, elmbyval, elmalign, + NULL)); } if (ndim < nSubscripts || ndim <= 0 || ndim > MAXDIM) @@ -3328,7 +3331,8 @@ construct_array(Datum *elems, int nelems, lbs[0] = 1; return construct_md_array(elems, NULL, 1, dims, lbs, - elmtype, elmlen, elmbyval, elmalign); + elmtype, elmlen, elmbyval, elmalign, + NULL); } /* @@ -3432,6 +3436,8 @@ construct_array_builtin(Datum *elems, int nelems, Oid elmtype) * elem values will be copied into the object even if pass-by-ref type. * Also note the result will be 0-D not ndims-D if any dims[i] = 0. * + * NULL is returned if an error occurs and escontext is valid. + * * NOTE: it would be cleaner to look up the elmlen/elmbval/elmalign info * from the system catalogs, given the elmtype. However, the caller is * in a better position to cache this info across multiple uses, or even @@ -3443,7 +3449,8 @@ construct_md_array(Datum *elems, int ndims, int *dims, int *lbs, - Oid elmtype, int elmlen, bool elmbyval, char elmalign) + Oid elmtype, int elmlen, bool elmbyval, char elmalign, + Node *escontext) { ArrayType *result; bool hasnulls; @@ -3453,15 +3460,18 @@ construct_md_array(Datum *elems, int nelems; if (ndims < 0) /* we do allow zero-dimension arrays */ - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid number of dimensions: %d", ndims))); if (ndims > MAXDIM) - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", ndims, MAXDIM))); + if (SOFT_ERROR_OCCURRED(escontext)) + return NULL; + /* This checks for overflow of the array dimensions */ nelems = ArrayGetNItems(ndims, dims); ArrayCheckBounds(ndims, dims, lbs); @@ -3485,7 +3495,13 @@ construct_md_array(Datum *elems, elems[i] = PointerGetDatum(PG_DETOAST_DATUM(elems[i])); nbytes = att_addlength_datum(nbytes, elmlen, elems[i]); nbytes = att_align_nominal(nbytes, elmalign); - /* check for overflow of total request */ + /* + * check for overflow of total request + * + * The following should be perhaps be errsave() to respect caller's + * wish to suppress the error, but it also doesn't seem like a good + * idea to ignore the problem being reported here. + */ if (!AllocSizeIsValid(nbytes)) ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), @@ -4690,7 +4706,8 @@ array_iterate(ArrayIterator iterator, Datum *value, bool *isnull) ARR_ELEMTYPE(iterator->arr), iterator->typlen, iterator->typbyval, - iterator->typalign); + iterator->typalign, + NULL); *isnull = false; *value = PointerGetDatum(result); @@ -5377,7 +5394,7 @@ makeArrayResult(ArrayBuildState *astate, lbs[0] = 1; return makeMdArrayResult(astate, ndims, dims, lbs, rcontext, - astate->private_cxt); + astate->private_cxt, NULL); } /* @@ -5401,7 +5418,8 @@ makeMdArrayResult(ArrayBuildState *astate, int *dims, int *lbs, MemoryContext rcontext, - bool release) + bool release, + Node *escontext) { ArrayType *result; MemoryContext oldcontext; @@ -5417,7 +5435,8 @@ makeMdArrayResult(ArrayBuildState *astate, astate->element_type, astate->typlen, astate->typbyval, - astate->typalign); + astate->typalign, + NULL); MemoryContextSwitchTo(oldcontext); @@ -5817,7 +5836,7 @@ makeArrayResultAny(ArrayBuildStateAny *astate, lbs[0] = 1; result = makeMdArrayResult(astate->scalarstate, ndims, dims, lbs, - rcontext, release); + rcontext, release, NULL); } else { diff --git a/src/backend/utils/adt/domains.c b/src/backend/utils/adt/domains.c index 8d766f68e3..f8669ee1b5 100644 --- a/src/backend/utils/adt/domains.c +++ b/src/backend/utils/adt/domains.c @@ -341,7 +341,8 @@ domain_recv(PG_FUNCTION_ARGS) */ void domain_check(Datum value, bool isnull, Oid domainType, - void **extra, MemoryContext mcxt) + void **extra, MemoryContext mcxt, + Node *escontext) { DomainIOData *my_extra = NULL; @@ -365,7 +366,7 @@ domain_check(Datum value, bool isnull, Oid domainType, /* * Do the necessary checks to ensure it's a valid domain value. */ - domain_check_input(value, isnull, my_extra, NULL); + domain_check_input(value, isnull, my_extra, escontext); } /* diff --git a/src/backend/utils/adt/expandedrecord.c b/src/backend/utils/adt/expandedrecord.c index c46e5aa36f..358ee015a5 100644 --- a/src/backend/utils/adt/expandedrecord.c +++ b/src/backend/utils/adt/expandedrecord.c @@ -1359,7 +1359,8 @@ expanded_record_set_fields(ExpandedRecordHeader *erh, domain_check(ExpandedRecordGetRODatum(erh), false, erh->er_decltypeid, &erh->er_domaininfo, - erh->hdr.eoh_context); + erh->hdr.eoh_context, + NULL); } MemoryContextSwitchTo(oldcxt); @@ -1561,7 +1562,8 @@ check_domain_for_new_field(ExpandedRecordHeader *erh, int fnumber, domain_check(ExpandedRecordGetRODatum(dummy_erh), false, erh->er_decltypeid, &erh->er_domaininfo, - erh->hdr.eoh_context); + erh->hdr.eoh_context, + NULL); MemoryContextSwitchTo(oldcxt); @@ -1587,7 +1589,8 @@ check_domain_for_new_tuple(ExpandedRecordHeader *erh, HeapTuple tuple) domain_check((Datum) 0, true, erh->er_decltypeid, &erh->er_domaininfo, - erh->hdr.eoh_context); + erh->hdr.eoh_context, + NULL); MemoryContextSwitchTo(oldcxt); @@ -1624,7 +1627,8 @@ check_domain_for_new_tuple(ExpandedRecordHeader *erh, HeapTuple tuple) domain_check(ExpandedRecordGetRODatum(dummy_erh), false, erh->er_decltypeid, &erh->er_domaininfo, - erh->hdr.eoh_context); + erh->hdr.eoh_context, + NULL); MemoryContextSwitchTo(oldcxt); diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index a4bfa5e404..cb9df6caf2 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -265,6 +265,7 @@ typedef struct PopulateArrayContext int *dims; /* dimensions */ int *sizes; /* current dimension counters */ int ndims; /* number of dimensions */ + Node *escontext; /* For soft-error handling */ } PopulateArrayContext; /* state for populate_array_json() */ @@ -389,7 +390,8 @@ static JsonParseErrorType elements_array_element_end(void *state, bool isnull); static JsonParseErrorType elements_scalar(void *state, char *token, JsonTokenType tokentype); /* turn a json object into a hash table */ -static HTAB *get_json_object_as_hash(char *json, int len, const char *funcname); +static HTAB *get_json_object_as_hash(char *json, int len, const char *funcname, + Node *escontext); /* semantic actions for populate_array_json */ static JsonParseErrorType populate_array_object_start(void *_state); @@ -431,37 +433,42 @@ static Datum populate_record_worker(FunctionCallInfo fcinfo, const char *funcnam /* helper functions for populate_record[set] */ static HeapTupleHeader populate_record(TupleDesc tupdesc, RecordIOData **record_p, HeapTupleHeader defaultval, MemoryContext mcxt, - JsObject *obj); + JsObject *obj, Node *escontext); static void get_record_type_from_argument(FunctionCallInfo fcinfo, const char *funcname, PopulateRecordCache *cache); static void get_record_type_from_query(FunctionCallInfo fcinfo, const char *funcname, PopulateRecordCache *cache); -static void JsValueToJsObject(JsValue *jsv, JsObject *jso); +static bool JsValueToJsObject(JsValue *jsv, JsObject *jso, Node *escontext); static Datum populate_composite(CompositeIOData *io, Oid typid, const char *colname, MemoryContext mcxt, - HeapTupleHeader defaultval, JsValue *jsv, bool isnull); -static Datum populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv); + HeapTupleHeader defaultval, JsValue *jsv, bool *isnull, + Node *escontext); +static Datum populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv, + bool *isnull, Node *escontext); static void prepare_column_cache(ColumnIOData *column, Oid typid, int32 typmod, MemoryContext mcxt, bool need_scalar); static Datum populate_record_field(ColumnIOData *col, Oid typid, int32 typmod, const char *colname, MemoryContext mcxt, Datum defaultval, - JsValue *jsv, bool *isnull); + JsValue *jsv, bool *isnull, Node *escontext); static RecordIOData *allocate_record_info(MemoryContext mcxt, int ncolumns); static bool JsObjectGetField(JsObject *obj, char *field, JsValue *jsv); static void populate_recordset_record(PopulateRecordsetState *state, JsObject *obj); -static void populate_array_json(PopulateArrayContext *ctx, char *json, int len); -static void populate_array_dim_jsonb(PopulateArrayContext *ctx, JsonbValue *jbv, +static bool populate_array_json(PopulateArrayContext *ctx, char *json, int len); +static bool populate_array_dim_jsonb(PopulateArrayContext *ctx, JsonbValue *jbv, int ndim); static void populate_array_report_expected_array(PopulateArrayContext *ctx, int ndim); -static void populate_array_assign_ndims(PopulateArrayContext *ctx, int ndims); -static void populate_array_check_dimension(PopulateArrayContext *ctx, int ndim); -static void populate_array_element(PopulateArrayContext *ctx, int ndim, JsValue *jsv); +static bool populate_array_assign_ndims(PopulateArrayContext *ctx, int ndims); +static bool populate_array_check_dimension(PopulateArrayContext *ctx, int ndim); +static bool populate_array_element(PopulateArrayContext *ctx, int ndim, JsValue *jsv); static Datum populate_array(ArrayIOData *aio, const char *colname, - MemoryContext mcxt, JsValue *jsv); + MemoryContext mcxt, JsValue *jsv, + bool *isnull, + Node *escontext); static Datum populate_domain(DomainIOData *io, Oid typid, const char *colname, - MemoryContext mcxt, JsValue *jsv, bool isnull); + MemoryContext mcxt, JsValue *jsv, bool isnull, + Node *escontext); /* functions supporting jsonb_delete, jsonb_set and jsonb_concat */ static JsonbValue *IteratorConcat(JsonbIterator **it1, JsonbIterator **it2, @@ -2477,19 +2484,23 @@ json_to_record(PG_FUNCTION_ARGS) true, false); } -/* helper function for diagnostics */ +/* + * Helper function for diagnostics + * + * Returns false if the input is erratic. + */ static void populate_array_report_expected_array(PopulateArrayContext *ctx, int ndim) { if (ndim <= 0) { if (ctx->colname) - ereport(ERROR, + errsave(ctx->escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("expected JSON array"), errhint("See the value of key \"%s\".", ctx->colname))); else - ereport(ERROR, + errsave(ctx->escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("expected JSON array"))); } @@ -2506,13 +2517,13 @@ populate_array_report_expected_array(PopulateArrayContext *ctx, int ndim) appendStringInfo(&indices, "[%d]", ctx->sizes[i]); if (ctx->colname) - ereport(ERROR, + errsave(ctx->escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("expected JSON array"), errhint("See the array element %s of key \"%s\".", indices.data, ctx->colname))); else - ereport(ERROR, + errsave(ctx->escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("expected JSON array"), errhint("See the array element %s.", @@ -2520,8 +2531,12 @@ populate_array_report_expected_array(PopulateArrayContext *ctx, int ndim) } } -/* set the number of dimensions of the populated array when it becomes known */ -static void +/* + * Set the number of dimensions of the populated array when it becomes known. + * + * Returns false if the input (ndims) is erratic. + */ +static bool populate_array_assign_ndims(PopulateArrayContext *ctx, int ndims) { int i; @@ -2529,7 +2544,12 @@ populate_array_assign_ndims(PopulateArrayContext *ctx, int ndims) Assert(ctx->ndims <= 0); if (ndims <= 0) + { populate_array_report_expected_array(ctx, ndims); + /* Getting here means the error was reported softly. */ + Assert(SOFT_ERROR_OCCURRED(ctx->escontext)); + return false; + } ctx->ndims = ndims; ctx->dims = palloc(sizeof(int) * ndims); @@ -2537,10 +2557,16 @@ populate_array_assign_ndims(PopulateArrayContext *ctx, int ndims) for (i = 0; i < ndims; i++) ctx->dims[i] = -1; /* dimensions are unknown yet */ + + return true; } -/* check the populated subarray dimension */ -static void +/* + * Check the populated subarray dimension + * + * Returns false if the input (ndims) is erratic. + */ +static bool populate_array_check_dimension(PopulateArrayContext *ctx, int ndim) { int dim = ctx->sizes[ndim]; /* current dimension counter */ @@ -2548,21 +2574,31 @@ populate_array_check_dimension(PopulateArrayContext *ctx, int ndim) if (ctx->dims[ndim] == -1) ctx->dims[ndim] = dim; /* assign dimension if not yet known */ else if (ctx->dims[ndim] != dim) - ereport(ERROR, + errsave(ctx->escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed JSON array"), errdetail("Multidimensional arrays must have " "sub-arrays with matching dimensions."))); + /* Nothing to do on an error. */ + if (SOFT_ERROR_OCCURRED(ctx->escontext)) + return false; + /* reset the current array dimension size counter */ ctx->sizes[ndim] = 0; /* increment the parent dimension counter if it is a nested sub-array */ if (ndim > 0) ctx->sizes[ndim - 1]++; + + return true; } -static void +/* + * Returns true if the array element value was successfully extracted from jsv + * and added to ctx->astate. False if an error occurred when doing so. + */ +static bool populate_array_element(PopulateArrayContext *ctx, int ndim, JsValue *jsv) { Datum element; @@ -2573,13 +2609,18 @@ populate_array_element(PopulateArrayContext *ctx, int ndim, JsValue *jsv) ctx->aio->element_type, ctx->aio->element_typmod, NULL, ctx->mcxt, PointerGetDatum(NULL), - jsv, &element_isnull); + jsv, &element_isnull, ctx->escontext); + /* Nothing to do on an error. */ + if (SOFT_ERROR_OCCURRED(ctx->escontext)) + return false; accumArrayResult(ctx->astate, element, element_isnull, ctx->aio->element_type, ctx->acxt); Assert(ndim > 0); ctx->sizes[ndim - 1]++; /* increment current dimension counter */ + + return true; } /* json object start handler for populate_array_json() */ @@ -2594,6 +2635,10 @@ populate_array_object_start(void *_state) else if (ndim < state->ctx->ndims) populate_array_report_expected_array(state->ctx, ndim); + /* Report if an error occurred. */ + if (SOFT_ERROR_OCCURRED(state->ctx->escontext)) + return JSON_SEM_ACTION_FAILED; + return JSON_SUCCESS; } @@ -2609,7 +2654,11 @@ populate_array_array_end(void *_state) populate_array_assign_ndims(ctx, ndim + 1); if (ndim < ctx->ndims) - populate_array_check_dimension(ctx, ndim); + { + /* Report if an error occurred. */ + if (!populate_array_check_dimension(ctx, ndim)) + return JSON_SEM_ACTION_FAILED; + } return JSON_SUCCESS; } @@ -2667,7 +2716,8 @@ populate_array_element_end(void *_state, bool isnull) state->element_start) * sizeof(char); } - populate_array_element(ctx, ndim, &jsv); + if (!populate_array_element(ctx, ndim, &jsv)) + return JSON_SEM_ACTION_FAILED; } return JSON_SUCCESS; @@ -2686,6 +2736,10 @@ populate_array_scalar(void *_state, char *token, JsonTokenType tokentype) else if (ndim < ctx->ndims) populate_array_report_expected_array(ctx, ndim); + /* Report if an error occurred. */ + if (SOFT_ERROR_OCCURRED(ctx->escontext)) + return JSON_SEM_ACTION_FAILED; + if (ndim == ctx->ndims) { /* remember the scalar element token */ @@ -2697,8 +2751,12 @@ populate_array_scalar(void *_state, char *token, JsonTokenType tokentype) return JSON_SUCCESS; } -/* parse a json array and populate array */ -static void +/* + * Parse a json array and populate array + * + * Returns false if an error occurs when parsing. + */ +static bool populate_array_json(PopulateArrayContext *ctx, char *json, int len) { PopulateArrayState state; @@ -2715,19 +2773,25 @@ populate_array_json(PopulateArrayContext *ctx, char *json, int len) sem.array_element_end = populate_array_element_end; sem.scalar = populate_array_scalar; - pg_parse_json_or_ereport(state.lex, &sem); - - /* number of dimensions should be already known */ - Assert(ctx->ndims > 0 && ctx->dims); + if (pg_parse_json_or_errsave(state.lex, &sem, ctx->escontext)) + { + /* number of dimensions should be already known */ + Assert(ctx->ndims > 0 && ctx->dims); + } pfree(state.lex); + + return !SOFT_ERROR_OCCURRED(ctx->escontext); } /* * populate_array_dim_jsonb() -- Iterate recursively through jsonb sub-array * elements and accumulate result using given ArrayBuildState. + * + * Returns false if we return partway through because of an error in a + * subroutine. */ -static void +static bool populate_array_dim_jsonb(PopulateArrayContext *ctx, /* context */ JsonbValue *jbv, /* jsonb sub-array */ int ndim) /* current dimension */ @@ -2741,7 +2805,12 @@ populate_array_dim_jsonb(PopulateArrayContext *ctx, /* context */ check_stack_depth(); if (jbv->type != jbvBinary || !JsonContainerIsArray(jbc)) + { populate_array_report_expected_array(ctx, ndim - 1); + /* Getting here means the error was reported softly. */ + Assert(SOFT_ERROR_OCCURRED(ctx->escontext)); + return false; + } Assert(!JsonContainerIsScalar(jbc)); @@ -2762,7 +2831,10 @@ populate_array_dim_jsonb(PopulateArrayContext *ctx, /* context */ (tok == WJB_ELEM && (val.type != jbvBinary || !JsonContainerIsArray(val.val.binary.data))))) - populate_array_assign_ndims(ctx, ndim); + { + if (!populate_array_assign_ndims(ctx, ndim)) + return false; + } jsv.is_json = false; jsv.val.jsonb = &val; @@ -2775,16 +2847,21 @@ populate_array_dim_jsonb(PopulateArrayContext *ctx, /* context */ * it is not the innermost dimension. */ if (ctx->ndims > 0 && ndim >= ctx->ndims) - populate_array_element(ctx, ndim, &jsv); + { + if (!populate_array_element(ctx, ndim, &jsv)) + return false; + } else { - /* populate child sub-array */ - populate_array_dim_jsonb(ctx, &val, ndim + 1); + /* populate child sub-array; nothing to do on an error. */ + if (!populate_array_dim_jsonb(ctx, &val, ndim + 1)) + return false; /* number of dimensions should be already known */ Assert(ctx->ndims > 0 && ctx->dims); - populate_array_check_dimension(ctx, ndim); + if (!populate_array_check_dimension(ctx, ndim)) + return false; } tok = JsonbIteratorNext(&it, &val, true); @@ -2795,14 +2872,22 @@ populate_array_dim_jsonb(PopulateArrayContext *ctx, /* context */ /* free iterator, iterating until WJB_DONE */ tok = JsonbIteratorNext(&it, &val, true); Assert(tok == WJB_DONE && !it); + + return true; } -/* recursively populate an array from json/jsonb */ +/* + * Recursively populate an array from json/jsonb + * + * *isnull is set to true if an error is reported during parsing. + */ static Datum populate_array(ArrayIOData *aio, const char *colname, MemoryContext mcxt, - JsValue *jsv) + JsValue *jsv, + bool *isnull, + Node *escontext) { PopulateArrayContext ctx; Datum result; @@ -2817,14 +2902,26 @@ populate_array(ArrayIOData *aio, ctx.ndims = 0; /* unknown yet */ ctx.dims = NULL; ctx.sizes = NULL; + ctx.escontext = escontext; if (jsv->is_json) - populate_array_json(&ctx, jsv->val.json.str, - jsv->val.json.len >= 0 ? jsv->val.json.len - : strlen(jsv->val.json.str)); + { + if (!populate_array_json(&ctx, jsv->val.json.str, + jsv->val.json.len >= 0 ? jsv->val.json.len + : strlen(jsv->val.json.str))) + { + *isnull = true; + return (Datum) 0; + } + } else { - populate_array_dim_jsonb(&ctx, jsv->val.jsonb, 1); + /* Nothing to do on an error. */ + if (!populate_array_dim_jsonb(&ctx, jsv->val.jsonb, 1)) + { + *isnull = true; + return (Datum) 0; + } ctx.dims[0] = ctx.sizes[0]; } @@ -2836,17 +2933,22 @@ populate_array(ArrayIOData *aio, lbs[i] = 1; result = makeMdArrayResult(ctx.astate, ctx.ndims, ctx.dims, lbs, - ctx.acxt, true); + ctx.acxt, true, escontext); pfree(ctx.dims); pfree(ctx.sizes); pfree(lbs); + *isnull = false; return result; } -static void -JsValueToJsObject(JsValue *jsv, JsObject *jso) +/* + * Returns false if an error occurs, provided escontext points to an + * ErrorSaveContext. + */ +static bool +JsValueToJsObject(JsValue *jsv, JsObject *jso, Node *escontext) { jso->is_json = jsv->is_json; @@ -2858,7 +2960,8 @@ JsValueToJsObject(JsValue *jsv, JsObject *jso) jsv->val.json.len >= 0 ? jsv->val.json.len : strlen(jsv->val.json.str), - "populate_composite"); + "populate_composite", + escontext); } else { @@ -2876,7 +2979,7 @@ JsValueToJsObject(JsValue *jsv, JsObject *jso) is_scalar = IsAJsonbScalar(jbv) || (jbv->type == jbvBinary && JsonContainerIsScalar(jbv->val.binary.data)); - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), is_scalar ? errmsg("cannot call %s on a scalar", @@ -2885,6 +2988,8 @@ JsValueToJsObject(JsValue *jsv, JsObject *jso) "populate_composite"))); } } + + return !SOFT_ERROR_OCCURRED(escontext); } /* acquire or update cached tuple descriptor for a composite type */ @@ -2911,7 +3016,12 @@ update_cached_tupdesc(CompositeIOData *io, MemoryContext mcxt) } } -/* recursively populate a composite (row type) value from json/jsonb */ +/* + * Recursively populate a composite (row type) value from json/jsonb + * + * Returns null if an error occurs in a subroutine, provided escontext points + * to an ErrorSaveContext. + */ static Datum populate_composite(CompositeIOData *io, Oid typid, @@ -2919,14 +3029,15 @@ populate_composite(CompositeIOData *io, MemoryContext mcxt, HeapTupleHeader defaultval, JsValue *jsv, - bool isnull) + bool *isnull, + Node *escontext) { Datum result; /* acquire/update cached tuple descriptor */ update_cached_tupdesc(io, mcxt); - if (isnull) + if (*isnull) result = (Datum) 0; else { @@ -2934,11 +3045,15 @@ populate_composite(CompositeIOData *io, JsObject jso; /* prepare input value */ - JsValueToJsObject(jsv, &jso); + if (!JsValueToJsObject(jsv, &jso, escontext)) + { + *isnull = true; + return (Datum) 0; + } /* populate resulting record tuple */ tuple = populate_record(io->tupdesc, &io->record_io, - defaultval, mcxt, &jso); + defaultval, mcxt, &jso, escontext); result = HeapTupleHeaderGetDatum(tuple); JsObjectFree(&jso); @@ -2950,14 +3065,20 @@ populate_composite(CompositeIOData *io, * now, we can tell by comparing typid to base_typid.) */ if (typid != io->base_typid && typid != RECORDOID) - domain_check(result, isnull, typid, &io->domain_info, mcxt); + domain_check(result, *isnull, typid, &io->domain_info, mcxt, escontext); return result; } -/* populate non-null scalar value from json/jsonb value */ +/* + * Populate non-null scalar value from json/jsonb value. + * + * Returns null if an error occurs during the call to type input function, + * provided escontext is valid. + */ static Datum -populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv) +populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv, + bool *isnull, Node *escontext) { Datum res; char *str = NULL; @@ -3028,7 +3149,12 @@ populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv) elog(ERROR, "unrecognized jsonb type: %d", (int) jbv->type); } - res = InputFunctionCall(&io->typiofunc, str, io->typioparam, typmod); + if (!InputFunctionCallSafe(&io->typiofunc, str, io->typioparam, typmod, + escontext, &res)) + { + res = (Datum) 0; + *isnull = true; + } /* free temporary buffer */ if (str != json) @@ -3043,7 +3169,8 @@ populate_domain(DomainIOData *io, const char *colname, MemoryContext mcxt, JsValue *jsv, - bool isnull) + bool isnull, + Node *escontext) { Datum res; @@ -3054,11 +3181,11 @@ populate_domain(DomainIOData *io, res = populate_record_field(io->base_io, io->base_typid, io->base_typmod, colname, mcxt, PointerGetDatum(NULL), - jsv, &isnull); - Assert(!isnull); + jsv, &isnull, escontext); + Assert(!isnull || SOFT_ERROR_OCCURRED(escontext)); } - domain_check(res, isnull, typid, &io->domain_info, mcxt); + domain_check(res, isnull, typid, &io->domain_info, mcxt, escontext); return res; } @@ -3159,7 +3286,8 @@ populate_record_field(ColumnIOData *col, MemoryContext mcxt, Datum defaultval, JsValue *jsv, - bool *isnull) + bool *isnull, + Node *escontext) { TypeCat typcat; @@ -3192,10 +3320,12 @@ populate_record_field(ColumnIOData *col, switch (typcat) { case TYPECAT_SCALAR: - return populate_scalar(&col->scalar_io, typid, typmod, jsv); + return populate_scalar(&col->scalar_io, typid, typmod, jsv, + isnull, escontext); case TYPECAT_ARRAY: - return populate_array(&col->io.array, colname, mcxt, jsv); + return populate_array(&col->io.array, colname, mcxt, jsv, + isnull, escontext); case TYPECAT_COMPOSITE: case TYPECAT_COMPOSITE_DOMAIN: @@ -3204,11 +3334,12 @@ populate_record_field(ColumnIOData *col, DatumGetPointer(defaultval) ? DatumGetHeapTupleHeader(defaultval) : NULL, - jsv, *isnull); + jsv, isnull, + escontext); case TYPECAT_DOMAIN: return populate_domain(&col->io.domain, typid, colname, mcxt, - jsv, *isnull); + jsv, *isnull, escontext); default: elog(ERROR, "unrecognized type category '%c'", typcat); @@ -3265,7 +3396,8 @@ populate_record(TupleDesc tupdesc, RecordIOData **record_p, HeapTupleHeader defaultval, MemoryContext mcxt, - JsObject *obj) + JsObject *obj, + Node *escontext) { RecordIOData *record = *record_p; Datum *values; @@ -3357,7 +3489,8 @@ populate_record(TupleDesc tupdesc, mcxt, nulls[i] ? (Datum) 0 : values[i], &field, - &nulls[i]); + &nulls[i], + escontext); } res = heap_form_tuple(tupdesc, values, nulls); @@ -3444,6 +3577,7 @@ populate_record_worker(FunctionCallInfo fcinfo, const char *funcname, JsValue jsv = {0}; HeapTupleHeader rec; Datum rettuple; + bool isnull; JsonbValue jbv; MemoryContext fnmcxt = fcinfo->flinfo->fn_mcxt; PopulateRecordCache *cache = fcinfo->flinfo->fn_extra; @@ -3530,8 +3664,11 @@ populate_record_worker(FunctionCallInfo fcinfo, const char *funcname, jbv.val.binary.len = VARSIZE(jb) - VARHDRSZ; } + isnull = false; rettuple = populate_composite(&cache->c.io.composite, cache->argtype, - NULL, fnmcxt, rec, &jsv, false); + NULL, fnmcxt, rec, &jsv, &isnull, + NULL); + Assert(!isnull); PG_RETURN_DATUM(rettuple); } @@ -3540,9 +3677,13 @@ populate_record_worker(FunctionCallInfo fcinfo, const char *funcname, * get_json_object_as_hash * * decompose a json object into a hash table. + * + * Returns false if we return partway through because of an error + * in pg_parse_json_or_errsave() below. */ static HTAB * -get_json_object_as_hash(char *json, int len, const char *funcname) +get_json_object_as_hash(char *json, int len, const char *funcname, + Node *escontext) { HASHCTL ctl; HTAB *tab; @@ -3571,7 +3712,7 @@ get_json_object_as_hash(char *json, int len, const char *funcname) sem->object_field_start = hash_object_field_start; sem->object_field_end = hash_object_field_end; - pg_parse_json_or_ereport(lex, sem); + pg_parse_json_or_errsave(lex, sem, escontext); return tab; } @@ -3740,14 +3881,15 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj) &cache->c.io.composite.record_io, state->rec, cache->fn_mcxt, - obj); + obj, + NULL); /* if it's domain over composite, check domain constraints */ if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN) domain_check(HeapTupleHeaderGetDatum(tuphead), false, cache->argtype, &cache->c.io.composite.domain_info, - cache->fn_mcxt); + cache->fn_mcxt, NULL); /* ok, save into tuplestore */ tuple.t_len = HeapTupleHeaderGetDatumLength(tuphead); diff --git a/src/backend/utils/adt/orderedsetaggs.c b/src/backend/utils/adt/orderedsetaggs.c index 2582a5cf45..d90ec30a02 100644 --- a/src/backend/utils/adt/orderedsetaggs.c +++ b/src/backend/utils/adt/orderedsetaggs.c @@ -840,7 +840,8 @@ percentile_disc_multi_final(PG_FUNCTION_ARGS) osastate->qstate->sortColType, osastate->qstate->typLen, osastate->qstate->typByVal, - osastate->qstate->typAlign)); + osastate->qstate->typAlign, + NULL)); } /* @@ -996,7 +997,8 @@ percentile_cont_multi_final_common(FunctionCallInfo fcinfo, expect_type, typLen, typByVal, - typAlign)); + typAlign, + NULL)); } /* diff --git a/src/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c index 702cd52b6d..e507f43c8d 100644 --- a/src/backend/utils/adt/regexp.c +++ b/src/backend/utils/adt/regexp.c @@ -1665,7 +1665,7 @@ build_regexp_match_result(regexp_matches_ctx *matchctx) lbs[0] = 1; /* XXX: this hardcodes assumptions about the text type */ return construct_md_array(elems, nulls, 1, dims, lbs, - TEXTOID, -1, false, TYPALIGN_INT); + TEXTOID, -1, false, TYPALIGN_INT, NULL); } /* diff --git a/src/include/utils/array.h b/src/include/utils/array.h index b13dfb345e..c1abecd3fc 100644 --- a/src/include/utils/array.h +++ b/src/include/utils/array.h @@ -62,6 +62,7 @@ #define ARRAY_H #include "fmgr.h" +#include "nodes/nodes.h" #include "utils/expandeddatum.h" /* avoid including execnodes.h here */ @@ -393,7 +394,8 @@ extern ArrayType *construct_md_array(Datum *elems, int ndims, int *dims, int *lbs, - Oid elmtype, int elmlen, bool elmbyval, char elmalign); + Oid elmtype, int elmlen, bool elmbyval, char elmalign, + Node *escontext); extern ArrayType *construct_empty_array(Oid elmtype); extern ExpandedArrayHeader *construct_empty_expanded_array(Oid element_type, MemoryContext parentcontext, @@ -419,7 +421,8 @@ extern ArrayBuildState *accumArrayResult(ArrayBuildState *astate, extern Datum makeArrayResult(ArrayBuildState *astate, MemoryContext rcontext); extern Datum makeMdArrayResult(ArrayBuildState *astate, int ndims, - int *dims, int *lbs, MemoryContext rcontext, bool release); + int *dims, int *lbs, MemoryContext rcontext, bool release, + Node *escontext); extern ArrayBuildStateArr *initArrayResultArr(Oid array_type, Oid element_type, MemoryContext rcontext, bool subcontext); diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 2f8b46d6da..4e1e655652 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -27,7 +27,8 @@ extern bool parse_bool_with_len(const char *value, size_t len, bool *result); /* domains.c */ extern void domain_check(Datum value, bool isnull, Oid domainType, - void **extra, MemoryContext mcxt); + void **extra, MemoryContext mcxt, + Node *escontext); extern int errdatatype(Oid datatypeOid); extern int errdomainconstraint(Oid datatypeOid, const char *conname); diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c index 863864253f..5410a37068 100644 --- a/src/pl/plperl/plperl.c +++ b/src/pl/plperl/plperl.c @@ -1292,7 +1292,7 @@ plperl_array_to_datum(SV *src, Oid typid, int32 typmod) lbs[i] = 1; return makeMdArrayResult(astate, ndims, dims, lbs, - CurrentMemoryContext, true); + CurrentMemoryContext, true, NULL); } /* Get the information needed to convert data to the specified PG type */ @@ -1403,7 +1403,7 @@ plperl_sv_to_datum(SV *sv, Oid typid, int32 typmod, ret = plperl_hash_to_datum(sv, td); if (isdomain) - domain_check(ret, false, typid, NULL, NULL); + domain_check(ret, false, typid, NULL, NULL, NULL); /* Release on the result of get_call_result_type is harmless */ ReleaseTupleDesc(td); @@ -3373,7 +3373,8 @@ plperl_return_next_internal(SV *sv) domain_check(HeapTupleGetDatum(tuple), false, current_call_data->cdomain_oid, ¤t_call_data->cdomain_info, - rsi->econtext->ecxt_per_query_memory); + rsi->econtext->ecxt_per_query_memory, + NULL); tuplestore_puttuple(current_call_data->tuple_store, tuple); } diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 4b76f7699a..0c5d5263c5 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -713,7 +713,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo, /* and check domain constraints */ /* XXX allowing caching here would be good, too */ domain_check(estate.retval, false, resultTypeId, - NULL, NULL); + NULL, NULL, NULL); break; case TYPEFUNC_RECORD: @@ -1494,7 +1494,8 @@ plpgsql_fulfill_promise(PLpgSQL_execstate *estate, PointerGetDatum(construct_md_array(elems, NULL, 1, dims, lbs, TEXTOID, - -1, false, TYPALIGN_INT)), + -1, false, TYPALIGN_INT, + NULL)), false, true); } else diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c index db14c5f8da..886118ce01 100644 --- a/src/pl/plpython/plpy_typeio.c +++ b/src/pl/plpython/plpy_typeio.c @@ -1104,7 +1104,8 @@ PLyObject_ToDomain(PLyObToDatum *arg, PyObject *plrv, result = base->func(base, plrv, isnull, inarray); domain_check(result, *isnull, arg->typoid, - &arg->u.domain.domain_info, arg->mcxt); + &arg->u.domain.domain_info, arg->mcxt, + NULL); return result; } @@ -1177,7 +1178,7 @@ PLySequence_ToArray(PLyObToDatum *arg, PyObject *plrv, lbs[i] = 1; return makeMdArrayResult(astate, ndims, dims, lbs, - CurrentMemoryContext, true); + CurrentMemoryContext, true, NULL); } /* diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c index e8f9d7b289..a7adb42ff0 100644 --- a/src/pl/tcl/pltcl.c +++ b/src/pl/tcl/pltcl.c @@ -3240,7 +3240,8 @@ pltcl_build_tuple_result(Tcl_Interp *interp, Tcl_Obj **kvObjv, int kvObjc, domain_check(HeapTupleGetDatum(tuple), false, call_state->prodesc->result_typid, &call_state->prodesc->domain_info, - call_state->prodesc->fn_cxt); + call_state->prodesc->fn_cxt, + NULL); return tuple; } diff --git a/src/test/modules/test_regex/test_regex.c b/src/test/modules/test_regex/test_regex.c index d1dd48a993..fb8a22f891 100644 --- a/src/test/modules/test_regex/test_regex.c +++ b/src/test/modules/test_regex/test_regex.c @@ -679,7 +679,8 @@ build_test_info_result(regex_t *cpattern, test_re_flags *flags) lbs[0] = 1; /* XXX: this hardcodes assumptions about the text type */ return construct_md_array(elems, NULL, 1, dims, lbs, - TEXTOID, -1, false, TYPALIGN_INT); + TEXTOID, -1, false, TYPALIGN_INT, + NULL); } /* @@ -759,5 +760,6 @@ build_test_match_result(test_regex_ctx *matchctx) lbs[0] = 1; /* XXX: this hardcodes assumptions about the text type */ return construct_md_array(elems, nulls, 1, dims, lbs, - TEXTOID, -1, false, TYPALIGN_INT); + TEXTOID, -1, false, TYPALIGN_INT, + NULL); } -- 2.35.3