From 6a55c4988f2baefa0f7e5c558352fb0bbb7bc8be Mon Sep 17 00:00:00 2001 From: Amit Langote Date: Mon, 11 Sep 2023 21:31:29 +0900 Subject: [PATCH v14 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 to pass arounf the ErrorSaveContext if one is received from an external caller. * 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. --- src/backend/utils/adt/jsonfuncs.c | 269 +++++++++++++++++++++++------- 1 file changed, 205 insertions(+), 64 deletions(-) diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index a4bfa5e404..161193865d 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() */ @@ -431,7 +432,7 @@ 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); @@ -441,27 +442,32 @@ static void get_record_type_from_query(FunctionCallInfo fcinfo, static void JsValueToJsObject(JsValue *jsv, JsObject *jso); 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 +2483,24 @@ json_to_record(PG_FUNCTION_ARGS) true, false); } -/* helper function for diagnostics */ +/* + * Helper function for diagnostics + * + * Reports the error or saves error data into ctx->escontext if it is an + * ErrorSaveContext. + */ 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,13 @@ 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, provided ctx->escontext + * points to an ErrorSaveContext. + */ +static bool populate_array_assign_ndims(PopulateArrayContext *ctx, int ndims) { int i; @@ -2529,7 +2545,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 +2558,17 @@ 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 (ndim) is erratic, provided ctx->escontext + * points to an ErrorSaveContext. + */ +static bool populate_array_check_dimension(PopulateArrayContext *ctx, int ndim) { int dim = ctx->sizes[ndim]; /* current dimension counter */ @@ -2548,21 +2576,33 @@ 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 +/* + * Populates an array element using value extracted from jsv. + * + * Returns false if an error occurs when doing so, provided ctx->escontext + * points to an ErrorSaveContext. + */ +static bool populate_array_element(PopulateArrayContext *ctx, int ndim, JsValue *jsv) { Datum element; @@ -2573,13 +2613,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 +2639,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 +2658,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 +2720,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 +2740,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 +2755,13 @@ populate_array_scalar(void *_state, char *token, JsonTokenType tokentype) return JSON_SUCCESS; } -/* parse a json array and populate array */ -static void +/* + * Parse a json value and populate array + * + * Returns false if an error occurs when parsing, provided ctx->escontext + * points to an ErrorSaveContext. + */ +static bool populate_array_json(PopulateArrayContext *ctx, char *json, int len) { PopulateArrayState state; @@ -2715,19 +2778,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, provided ctx->escontext points to an ErrorSaveContext. */ -static void +static bool populate_array_dim_jsonb(PopulateArrayContext *ctx, /* context */ JsonbValue *jbv, /* jsonb sub-array */ int ndim) /* current dimension */ @@ -2741,7 +2810,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 +2836,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 +2852,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 +2877,23 @@ 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 occurs during parsing, provided + * escontext is an ErrorSaveContext. + */ 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 +2908,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]; } @@ -2842,6 +2945,7 @@ populate_array(ArrayIOData *aio, pfree(ctx.sizes); pfree(lbs); + *isnull = false; return result; } @@ -2911,7 +3015,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 datum if an error occurs when populating the record, provided + * escontext points to an ErrorSaveContext. + */ static Datum populate_composite(CompositeIOData *io, Oid typid, @@ -2919,7 +3028,8 @@ populate_composite(CompositeIOData *io, MemoryContext mcxt, HeapTupleHeader defaultval, JsValue *jsv, - bool isnull) + bool isnull, + Node *escontext) { Datum result; @@ -2938,8 +3048,11 @@ populate_composite(CompositeIOData *io, /* populate resulting record tuple */ tuple = populate_record(io->tupdesc, &io->record_io, - defaultval, mcxt, &jso); - result = HeapTupleHeaderGetDatum(tuple); + defaultval, mcxt, &jso, escontext); + if (tuple) + result = HeapTupleHeaderGetDatum(tuple); + else + return (Datum) 0; JsObjectFree(&jso); } @@ -2955,9 +3068,15 @@ populate_composite(CompositeIOData *io, 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 points to an ErrorSaveContext. + */ 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 +3147,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 +3167,8 @@ populate_domain(DomainIOData *io, const char *colname, MemoryContext mcxt, JsValue *jsv, - bool isnull) + bool isnull, + Node *escontext) { Datum res; @@ -3054,7 +3179,7 @@ populate_domain(DomainIOData *io, res = populate_record_field(io->base_io, io->base_typid, io->base_typmod, colname, mcxt, PointerGetDatum(NULL), - jsv, &isnull); + jsv, &isnull, escontext); Assert(!isnull); } @@ -3159,7 +3284,8 @@ populate_record_field(ColumnIOData *col, MemoryContext mcxt, Datum defaultval, JsValue *jsv, - bool *isnull) + bool *isnull, + Node *escontext) { TypeCat typcat; @@ -3192,10 +3318,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 +3332,11 @@ 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); @@ -3259,18 +3387,24 @@ JsObjectGetField(JsObject *obj, char *field, JsValue *jsv) } } -/* populate a record tuple from json/jsonb value */ +/* + * Populate a record tuple from json/jsonb value. + * + * Returns NULL if an error occurs partway through initializing tuple fields, + * provided escontext points to an ErrorSaveContext. + */ static HeapTupleHeader populate_record(TupleDesc tupdesc, RecordIOData **record_p, HeapTupleHeader defaultval, MemoryContext mcxt, - JsObject *obj) + JsObject *obj, + Node *escontext) { RecordIOData *record = *record_p; Datum *values; bool *nulls; - HeapTuple res; + HeapTuple res = NULL; int ncolumns = tupdesc->natts; int i; @@ -3357,15 +3491,19 @@ populate_record(TupleDesc tupdesc, mcxt, nulls[i] ? (Datum) 0 : values[i], &field, - &nulls[i]); + &nulls[i], + escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + goto error; } res = heap_form_tuple(tupdesc, values, nulls); +error: pfree(values); pfree(nulls); - return res->t_data; + return res != NULL ? res->t_data : NULL; } /* @@ -3531,7 +3669,8 @@ populate_record_worker(FunctionCallInfo fcinfo, const char *funcname, } rettuple = populate_composite(&cache->c.io.composite, cache->argtype, - NULL, fnmcxt, rec, &jsv, false); + NULL, fnmcxt, rec, &jsv, false, + NULL); PG_RETURN_DATUM(rettuple); } @@ -3740,7 +3879,9 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj) &cache->c.io.composite.record_io, state->rec, cache->fn_mcxt, - obj); + obj, + NULL); + Assert(tuphead != NULL); /* if it's domain over composite, check domain constraints */ if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN) -- 2.35.3