diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 210c7c0b024..5030a1045f9 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -16727,6 +16727,58 @@ array w/o UK? | t + + + + jsonb_populate_record_valid + + jsonb_populate_record_valid ( base anyelement, from_json json ) + boolean + + + Function for testing jsonb_populate_record. Returns + true if the input jsonb_populate_record + would finish without an error for the given input JSON object; that is, it's + valid input, false otherwise. + + + create type jsb_char2 as (a char(2)); + + + select jsonb_populate_record_valid(NULL::jsb_char2, '{"a": "aaa"}'); + + + jsonb_populate_record_valid +----------------------------- + f +(1 row) + + + select * from jsonb_populate_record(NULL::jsb_char2, '{"a": "aaa"}') q; + + +ERROR: value too long for type character(2) + + select jsonb_populate_record_valid(NULL::jsb_char2, '{"a": "aa"}'); + + + jsonb_populate_record_valid +----------------------------- + t +(1 row) + + + select * from jsonb_populate_record(NULL::jsb_char2, '{"a": "aa"}') q; + + + a +---- + aa +(1 row) + + + + diff --git a/src/backend/utils/adt/domains.c b/src/backend/utils/adt/domains.c index 9f6e1a15adb..21791105da8 100644 --- a/src/backend/utils/adt/domains.c +++ b/src/backend/utils/adt/domains.c @@ -40,6 +40,9 @@ #include "utils/syscache.h" #include "utils/typcache.h" +static bool domain_check_internal(Datum value, bool isnull, Oid domainType, + void **extra, MemoryContext mcxt, + Node *escontext); /* * structure to cache state across multiple calls @@ -342,6 +345,32 @@ domain_recv(PG_FUNCTION_ARGS) void domain_check(Datum value, bool isnull, Oid domainType, void **extra, MemoryContext mcxt) +{ + (void) domain_check_internal(value, isnull, domainType, extra, mcxt, + NULL); +} + +/* Error-safe variant of domain_check(). */ +bool +domain_check_safe(Datum value, bool isnull, Oid domainType, + void **extra, MemoryContext mcxt, + Node *escontext) +{ + return domain_check_internal(value, isnull, domainType, extra, mcxt, + escontext); +} + +/* + * domain_check_internal + * Workhorse for domain_check() and domain_check_safe() + * + * Returns false if an error occurred in domain_check_input() and 'escontext' + * points to an ErrorSaveContext, true otherwise. + */ +static bool +domain_check_internal(Datum value, bool isnull, Oid domainType, + void **extra, MemoryContext mcxt, + Node *escontext) { DomainIOData *my_extra = NULL; @@ -365,7 +394,9 @@ 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); + + return !SOFT_ERROR_OCCURRED(escontext); } /* diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index caaafb72c07..6bfaf3703de 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); @@ -426,42 +428,48 @@ static JsonParseErrorType sn_scalar(void *state, char *token, JsonTokenType toke static Datum populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname, bool is_json, bool have_record_arg); static Datum populate_record_worker(FunctionCallInfo fcinfo, const char *funcname, - bool is_json, bool have_record_arg); + bool is_json, bool have_record_arg, + Node *escontext); /* 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, @@ -2453,28 +2461,45 @@ Datum jsonb_populate_record(PG_FUNCTION_ARGS) { return populate_record_worker(fcinfo, "jsonb_populate_record", - false, true); + false, true, NULL); +} + +/* + * SQL function that can be used for testing json_populate_record(). + * + * Returns false if json_populate_record() encounters an error for the + * provided input JSON object, true otherwise. + */ +Datum +jsonb_populate_record_valid(PG_FUNCTION_ARGS) +{ + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + (void) populate_record_worker(fcinfo, "jsonb_populate_record", + false, true, (Node *) &escontext); + + return BoolGetDatum(!SOFT_ERROR_OCCURRED(&escontext)); } Datum jsonb_to_record(PG_FUNCTION_ARGS) { return populate_record_worker(fcinfo, "jsonb_to_record", - false, false); + false, false, NULL); } Datum json_populate_record(PG_FUNCTION_ARGS) { return populate_record_worker(fcinfo, "json_populate_record", - true, true); + true, true, NULL); } Datum json_to_record(PG_FUNCTION_ARGS) { return populate_record_worker(fcinfo, "json_to_record", - true, false); + true, false, NULL); } /* helper function for diagnostics */ @@ -2484,14 +2509,15 @@ 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"))); + return; } else { @@ -2506,22 +2532,28 @@ 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.", indices.data))); + return; } } -/* set the number of dimensions of the populated array when it becomes known */ -static void +/* + * Validate and set ndims for populating an array with some + * populate_array_*() function. + * + * Returns false if the input (ndims) is erroneous. + */ +static bool populate_array_assign_ndims(PopulateArrayContext *ctx, int ndims) { int i; @@ -2529,7 +2561,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 +2574,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 erroneous. + */ +static bool populate_array_check_dimension(PopulateArrayContext *ctx, int ndim) { int dim = ctx->sizes[ndim]; /* current dimension counter */ @@ -2548,7 +2591,7 @@ 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, + ereturn(ctx->escontext, false, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed JSON array"), errdetail("Multidimensional arrays must have " @@ -2560,9 +2603,15 @@ populate_array_check_dimension(PopulateArrayContext *ctx, int ndim) /* 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 +2622,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() */ @@ -2590,9 +2644,17 @@ populate_array_object_start(void *_state) int ndim = state->lex->lex_level; if (state->ctx->ndims <= 0) - populate_array_assign_ndims(state->ctx, ndim); + { + if (!populate_array_assign_ndims(state->ctx, ndim)) + return JSON_SEM_ACTION_FAILED; + } else if (ndim < state->ctx->ndims) + { populate_array_report_expected_array(state->ctx, ndim); + /* Getting here means the error was reported softly. */ + Assert(SOFT_ERROR_OCCURRED(state->ctx->escontext)); + return JSON_SEM_ACTION_FAILED; + } return JSON_SUCCESS; } @@ -2606,10 +2668,17 @@ populate_array_array_end(void *_state) int ndim = state->lex->lex_level; if (ctx->ndims <= 0) - populate_array_assign_ndims(ctx, ndim + 1); + { + if (!populate_array_assign_ndims(ctx, ndim + 1)) + return JSON_SEM_ACTION_FAILED; + } 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 +2736,9 @@ populate_array_element_end(void *_state, bool isnull) state->element_start) * sizeof(char); } - populate_array_element(ctx, ndim, &jsv); + /* Report if an error occurred. */ + if (!populate_array_element(ctx, ndim, &jsv)) + return JSON_SEM_ACTION_FAILED; } return JSON_SUCCESS; @@ -2682,9 +2753,17 @@ populate_array_scalar(void *_state, char *token, JsonTokenType tokentype) int ndim = state->lex->lex_level; if (ctx->ndims <= 0) - populate_array_assign_ndims(ctx, ndim); + { + if (!populate_array_assign_ndims(ctx, ndim)) + return JSON_SEM_ACTION_FAILED; + } else if (ndim < ctx->ndims) + { populate_array_report_expected_array(ctx, ndim); + /* Getting here means the error was reported softly. */ + Assert(SOFT_ERROR_OCCURRED(ctx->escontext)); + return JSON_SEM_ACTION_FAILED; + } if (ndim == ctx->ndims) { @@ -2697,8 +2776,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; @@ -2716,19 +2799,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); + } freeJsonLexContext(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 */ @@ -2742,7 +2831,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)); @@ -2763,7 +2857,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; @@ -2776,16 +2873,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); + 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); @@ -2796,14 +2898,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; @@ -2818,14 +2928,27 @@ 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)); + { + /* Return null if an error was found. */ + 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); + /* Return null if an error was found. */ + if (!populate_array_dim_jsonb(&ctx, jsv->val.jsonb, 1)) + { + *isnull = true; + return (Datum) 0; + } ctx.dims[0] = ctx.sizes[0]; } @@ -2843,11 +2966,16 @@ populate_array(ArrayIOData *aio, 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; @@ -2859,7 +2987,9 @@ JsValueToJsObject(JsValue *jsv, JsObject *jso) jsv->val.json.len >= 0 ? jsv->val.json.len : strlen(jsv->val.json.str), - "populate_composite"); + "populate_composite", + escontext); + Assert(jso->val.json_hash != NULL || SOFT_ERROR_OCCURRED(escontext)); } else { @@ -2877,7 +3007,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", @@ -2886,6 +3016,8 @@ JsValueToJsObject(JsValue *jsv, JsObject *jso) "populate_composite"))); } } + + return !SOFT_ERROR_OCCURRED(escontext); } /* acquire or update cached tuple descriptor for a composite type */ @@ -2912,7 +3044,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, @@ -2920,14 +3057,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 { @@ -2935,11 +3073,21 @@ 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); + + if (SOFT_ERROR_OCCURRED(escontext)) + { + *isnull = true; + return (Datum) 0; + } result = HeapTupleHeaderGetDatum(tuple); JsObjectFree(&jso); @@ -2951,14 +3099,27 @@ 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); + { + if (!domain_check_safe(result, *isnull, typid, &io->domain_info, mcxt, + escontext)) + { + *isnull = true; + return (Datum) 0; + } + } 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; @@ -3029,7 +3190,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) @@ -3044,22 +3210,28 @@ populate_domain(DomainIOData *io, const char *colname, MemoryContext mcxt, JsValue *jsv, - bool isnull) + bool *isnull, + Node *escontext) { Datum res; - if (isnull) + if (*isnull) res = (Datum) 0; else { 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); + if (!domain_check_safe(res, *isnull, typid, &io->domain_info, mcxt, + escontext)) + { + *isnull = true; + return (Datum) 0; + } return res; } @@ -3160,7 +3332,8 @@ populate_record_field(ColumnIOData *col, MemoryContext mcxt, Datum defaultval, JsValue *jsv, - bool *isnull) + bool *isnull, + Node *escontext) { TypeCat typcat; @@ -3193,10 +3366,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: @@ -3205,11 +3380,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); @@ -3266,7 +3442,8 @@ populate_record(TupleDesc tupdesc, RecordIOData **record_p, HeapTupleHeader defaultval, MemoryContext mcxt, - JsObject *obj) + JsObject *obj, + Node *escontext) { RecordIOData *record = *record_p; Datum *values; @@ -3358,7 +3535,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); @@ -3439,12 +3617,14 @@ get_record_type_from_query(FunctionCallInfo fcinfo, */ static Datum populate_record_worker(FunctionCallInfo fcinfo, const char *funcname, - bool is_json, bool have_record_arg) + bool is_json, bool have_record_arg, + Node *escontext) { int json_arg_num = have_record_arg ? 1 : 0; JsValue jsv = {0}; HeapTupleHeader rec; Datum rettuple; + bool isnull; JsonbValue jbv; MemoryContext fnmcxt = fcinfo->flinfo->fn_mcxt; PopulateRecordCache *cache = fcinfo->flinfo->fn_extra; @@ -3531,8 +3711,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, + escontext); + Assert(!isnull || SOFT_ERROR_OCCURRED(escontext)); PG_RETURN_DATUM(rettuple); } @@ -3540,10 +3723,13 @@ populate_record_worker(FunctionCallInfo fcinfo, const char *funcname, /* * get_json_object_as_hash * - * decompose a json object into a hash table. + * Decomposes a json object into a hash table. + * + * Returns the hash table if the json is parsed successfully, NULL otherwise. */ 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; @@ -3572,7 +3758,11 @@ 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(state->lex, sem); + if (!pg_parse_json_or_errsave(state->lex, sem, escontext)) + { + hash_destroy(state->hash); + tab = NULL; + } freeJsonLexContext(state->lex); @@ -3743,14 +3933,16 @@ 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); + (void) domain_check_safe(HeapTupleHeaderGetDatum(tuphead), false, + cache->argtype, + &cache->c.io.composite.domain_info, + cache->fn_mcxt, + NULL); /* ok, save into tuplestore */ tuple.t_len = HeapTupleHeaderGetDatumLength(tuphead); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index ad74e07dbbd..e4115cd0840 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10032,6 +10032,10 @@ proname => 'jsonb_populate_record', proisstrict => 'f', provolatile => 's', prorettype => 'anyelement', proargtypes => 'anyelement jsonb', prosrc => 'jsonb_populate_record' }, +{ oid => '9558', descr => 'test get record fields from a jsonb object', + proname => 'jsonb_populate_record_valid', proisstrict => 'f', provolatile => 's', + prorettype => 'bool', proargtypes => 'anyelement jsonb', + prosrc => 'jsonb_populate_record_valid' }, { oid => '3475', descr => 'get set of records with fields from a jsonb array of objects', proname => 'jsonb_populate_recordset', prorows => '100', proisstrict => 'f', diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index f2ebbc56259..359c570f23e 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -28,6 +28,9 @@ 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); +extern bool domain_check_safe(Datum value, bool isnull, Oid domainType, + void **extra, MemoryContext mcxt, + Node *escontext); extern int errdatatype(Oid datatypeOid); extern int errdomainconstraint(Oid datatypeOid, const char *conname); diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out index b597d01a55e..66bee5162b4 100644 --- a/src/test/regress/expected/jsonb.out +++ b/src/test/regress/expected/jsonb.out @@ -2505,6 +2505,121 @@ SELECT rec FROM jsonb_populate_record( (abc,3,"Thu Jan 02 00:00:00 2003") (1 row) +-- Tests to check soft-error support for populate_record_field() +-- populate_scalar() +create type jsb_char2 as (a char(2)); +select jsonb_populate_record_valid(NULL::jsb_char2, '{"a": "aaa"}'); + jsonb_populate_record_valid +----------------------------- + f +(1 row) + +select * from jsonb_populate_record(NULL::jsb_char2, '{"a": "aaa"}') q; +ERROR: value too long for type character(2) +select jsonb_populate_record_valid(NULL::jsb_char2, '{"a": "aa"}'); + jsonb_populate_record_valid +----------------------------- + t +(1 row) + +select * from jsonb_populate_record(NULL::jsb_char2, '{"a": "aa"}') q; + a +---- + aa +(1 row) + +-- populate_array() +create type jsb_ia as (a int[]); +create type jsb_ia2 as (a int[][]); +select jsonb_populate_record_valid(NULL::jsb_ia, '{"a": 43.2}'); + jsonb_populate_record_valid +----------------------------- + f +(1 row) + +select * from jsonb_populate_record(NULL::jsb_ia, '{"a": 43.2}') q; +ERROR: expected JSON array +HINT: See the value of key "a". +select jsonb_populate_record_valid(NULL::jsb_ia, '{"a": [1, 2]}'); + jsonb_populate_record_valid +----------------------------- + t +(1 row) + +select * from jsonb_populate_record(NULL::jsb_ia, '{"a": [1, 2]}') q; + a +------- + {1,2} +(1 row) + +select jsonb_populate_record_valid(NULL::jsb_ia2, '{"a": [[1], [2, 3]]}'); + jsonb_populate_record_valid +----------------------------- + f +(1 row) + +select * from jsonb_populate_record(NULL::jsb_ia2, '{"a": [[1], [2, 3]]}') q; +ERROR: malformed JSON array +DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions. +select jsonb_populate_record_valid(NULL::jsb_ia2, '{"a": [[1, 0], [2, 3]]}'); + jsonb_populate_record_valid +----------------------------- + t +(1 row) + +select * from jsonb_populate_record(NULL::jsb_ia2, '{"a": [[1, 0], [2, 3]]}') q; + a +--------------- + {{1,0},{2,3}} +(1 row) + +-- populate_domain() +create domain jsb_i_not_null as int not null; +create domain jsb_i_gt_1 as int check (value > 1); +create type jsb_i_not_null_rec as (a jsb_i_not_null); +create type jsb_i_gt_1_rec as (a jsb_i_gt_1); +select jsonb_populate_record_valid(NULL::jsb_i_not_null_rec, '{"a": null}'); + jsonb_populate_record_valid +----------------------------- + f +(1 row) + +select * from jsonb_populate_record(NULL::jsb_i_not_null_rec, '{"a": null}') q; +ERROR: domain jsb_i_not_null does not allow null values +select jsonb_populate_record_valid(NULL::jsb_i_not_null_rec, '{"a": 1}'); + jsonb_populate_record_valid +----------------------------- + t +(1 row) + +select * from jsonb_populate_record(NULL::jsb_i_not_null_rec, '{"a": 1}') q; + a +--- + 1 +(1 row) + +select jsonb_populate_record_valid(NULL::jsb_i_gt_1_rec, '{"a": 1}'); + jsonb_populate_record_valid +----------------------------- + f +(1 row) + +select * from jsonb_populate_record(NULL::jsb_i_gt_1_rec, '{"a": 1}') q; +ERROR: value for domain jsb_i_gt_1 violates check constraint "jsb_i_gt_1_check" +select jsonb_populate_record_valid(NULL::jsb_i_gt_1_rec, '{"a": 2}'); + jsonb_populate_record_valid +----------------------------- + t +(1 row) + +select * from jsonb_populate_record(NULL::jsb_i_gt_1_rec, '{"a": 2}') q; + a +--- + 2 +(1 row) + +drop type jsb_ia, jsb_ia2, jsb_char2, jsb_i_not_null_rec, jsb_i_gt_1_rec; +drop domain jsb_i_not_null, jsb_i_gt_1; -- anonymous record type SELECT jsonb_populate_record(null::record, '{"x": 0, "y": 1}'); ERROR: could not determine row type for result of jsonb_populate_record diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql index 6dae715afd8..97bc2242a13 100644 --- a/src/test/regress/sql/jsonb.sql +++ b/src/test/regress/sql/jsonb.sql @@ -679,6 +679,43 @@ SELECT rec FROM jsonb_populate_record( '{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}' ) q; +-- Tests to check soft-error support for populate_record_field() + +-- populate_scalar() +create type jsb_char2 as (a char(2)); +select jsonb_populate_record_valid(NULL::jsb_char2, '{"a": "aaa"}'); +select * from jsonb_populate_record(NULL::jsb_char2, '{"a": "aaa"}') q; +select jsonb_populate_record_valid(NULL::jsb_char2, '{"a": "aa"}'); +select * from jsonb_populate_record(NULL::jsb_char2, '{"a": "aa"}') q; + +-- populate_array() +create type jsb_ia as (a int[]); +create type jsb_ia2 as (a int[][]); +select jsonb_populate_record_valid(NULL::jsb_ia, '{"a": 43.2}'); +select * from jsonb_populate_record(NULL::jsb_ia, '{"a": 43.2}') q; +select jsonb_populate_record_valid(NULL::jsb_ia, '{"a": [1, 2]}'); +select * from jsonb_populate_record(NULL::jsb_ia, '{"a": [1, 2]}') q; +select jsonb_populate_record_valid(NULL::jsb_ia2, '{"a": [[1], [2, 3]]}'); +select * from jsonb_populate_record(NULL::jsb_ia2, '{"a": [[1], [2, 3]]}') q; +select jsonb_populate_record_valid(NULL::jsb_ia2, '{"a": [[1, 0], [2, 3]]}'); +select * from jsonb_populate_record(NULL::jsb_ia2, '{"a": [[1, 0], [2, 3]]}') q; + +-- populate_domain() +create domain jsb_i_not_null as int not null; +create domain jsb_i_gt_1 as int check (value > 1); +create type jsb_i_not_null_rec as (a jsb_i_not_null); +create type jsb_i_gt_1_rec as (a jsb_i_gt_1); +select jsonb_populate_record_valid(NULL::jsb_i_not_null_rec, '{"a": null}'); +select * from jsonb_populate_record(NULL::jsb_i_not_null_rec, '{"a": null}') q; +select jsonb_populate_record_valid(NULL::jsb_i_not_null_rec, '{"a": 1}'); +select * from jsonb_populate_record(NULL::jsb_i_not_null_rec, '{"a": 1}') q; +select jsonb_populate_record_valid(NULL::jsb_i_gt_1_rec, '{"a": 1}'); +select * from jsonb_populate_record(NULL::jsb_i_gt_1_rec, '{"a": 1}') q; +select jsonb_populate_record_valid(NULL::jsb_i_gt_1_rec, '{"a": 2}'); +select * from jsonb_populate_record(NULL::jsb_i_gt_1_rec, '{"a": 2}') q; +drop type jsb_ia, jsb_ia2, jsb_char2, jsb_i_not_null_rec, jsb_i_gt_1_rec; +drop domain jsb_i_not_null, jsb_i_gt_1; + -- anonymous record type SELECT jsonb_populate_record(null::record, '{"x": 0, "y": 1}'); SELECT jsonb_populate_record(row(1,2), '{"f1": 0, "f2": 1}');