Re: Error-safe user functions - Mailing list pgsql-hackers
From | Tom Lane |
---|---|
Subject | Re: Error-safe user functions |
Date | |
Msg-id | 1436686.1670701118@sss.pgh.pa.us Whole thread Raw |
In response to | Re: Error-safe user functions (Andrew Dunstan <andrew@dunslane.net>) |
Responses |
Re: Error-safe user functions
|
List | pgsql-hackers |
Andrew Dunstan <andrew@dunslane.net> writes: > OK, json is a fairly easy case, see attached. But jsonb is a different > kettle of fish. Both the semantic routines called by the parser and the > subsequent call to JsonbValueToJsonb() can raise errors. These are > pretty much all about breaking various limits (for strings, objects, > arrays). There's also a call to numeric_in, but I assume that a string > that's already parsed as a valid json numeric literal won't upset > numeric_in. Um, nope ... regression=# select '1e1000000'::jsonb; ERROR: value overflows numeric format LINE 1: select '1e1000000'::jsonb; ^ > Many of these occur several calls down the stack, so > adjusting everything to deal with them would be fairly invasive. Perhaps > we could instead document that this class of input error won't be > trapped, at least for jsonb. Seeing that SQL/JSON is one of the major drivers of this whole project, it seemed a little sad to me that jsonb couldn't manage to implement what is required. So I spent a bit of time poking at it. Attached is an extended version of your patch that also covers jsonb. The main thing I soon realized is that the JsonSemAction API is based on the assumption that semantic actions will report errors by throwing them. This is a bit schizophrenic considering the parser itself carefully hands back error codes instead of throwing anything (excluding palloc failures of course). What I propose in the attached is that we change that API so that action functions return JsonParseErrorType, and add an enum value denoting "I already logged a suitable error, so you don't have to". It was a little tedious to modify all the existing functions that way, but not hard. Only the ones used by jsonb_in need to do anything except "return JSON_SUCCESS", at least for now. (I wonder if pg_verifybackup's parse_manifest.c could use a second look at how it's handling errors, given this API. I didn't study it closely.) I have not done anything here about errors within JsonbValueToJsonb. There would need to be another round of API-extension in that area if we want to be able to trap its errors. As you say, those are mostly about exceeding implementation size limits, so I suppose one could argue that they are not so different from palloc failure. It's still annoying. If people are good with the changes attached, I might take a look at that. regards, tom lane diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index fee2ffb55c..e6896eccfe 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -81,9 +81,10 @@ json_in(PG_FUNCTION_ARGS) /* validate it */ lex = makeJsonLexContext(result, false); - pg_parse_json_or_ereport(lex, &nullSemAction); + if (!pg_parse_json_or_errsave(lex, &nullSemAction, fcinfo->context)) + PG_RETURN_NULL(); - /* Internal representation is the same as text, for now */ + /* Internal representation is the same as text */ PG_RETURN_TEXT_P(result); } @@ -1337,7 +1338,7 @@ json_typeof(PG_FUNCTION_ARGS) /* Lex exactly one token from the input and check its type. */ result = json_lex(lex); if (result != JSON_SUCCESS) - json_ereport_error(result, lex); + json_errsave_error(result, lex, NULL); tok = lex->token_type; switch (tok) { diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index 9e14922ec2..7c1e5e6144 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -33,6 +33,7 @@ typedef struct JsonbInState { JsonbParseState *parseState; JsonbValue *res; + Node *escontext; } JsonbInState; /* unlike with json categories, we need to treat json and jsonb differently */ @@ -61,15 +62,15 @@ typedef struct JsonbAggState Oid val_output_func; } JsonbAggState; -static inline Datum jsonb_from_cstring(char *json, int len); -static size_t checkStringLen(size_t len); -static void jsonb_in_object_start(void *pstate); -static void jsonb_in_object_end(void *pstate); -static void jsonb_in_array_start(void *pstate); -static void jsonb_in_array_end(void *pstate); -static void jsonb_in_object_field_start(void *pstate, char *fname, bool isnull); +static inline Datum jsonb_from_cstring(char *json, int len, Node *escontext); +static bool checkStringLen(size_t len, Node *escontext); +static JsonParseErrorType jsonb_in_object_start(void *pstate); +static JsonParseErrorType jsonb_in_object_end(void *pstate); +static JsonParseErrorType jsonb_in_array_start(void *pstate); +static JsonParseErrorType jsonb_in_array_end(void *pstate); +static JsonParseErrorType jsonb_in_object_field_start(void *pstate, char *fname, bool isnull); static void jsonb_put_escaped_value(StringInfo out, JsonbValue *scalarVal); -static void jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype); +static JsonParseErrorType jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype); static void jsonb_categorize_type(Oid typoid, JsonbTypeCategory *tcategory, Oid *outfuncoid); @@ -98,7 +99,7 @@ jsonb_in(PG_FUNCTION_ARGS) { char *json = PG_GETARG_CSTRING(0); - return jsonb_from_cstring(json, strlen(json)); + return jsonb_from_cstring(json, strlen(json), fcinfo->context); } /* @@ -122,7 +123,7 @@ jsonb_recv(PG_FUNCTION_ARGS) else elog(ERROR, "unsupported jsonb version number %d", version); - return jsonb_from_cstring(str, nbytes); + return jsonb_from_cstring(str, nbytes, NULL); } /* @@ -251,9 +252,12 @@ jsonb_typeof(PG_FUNCTION_ARGS) * Turns json string into a jsonb Datum. * * Uses the json parser (with hooks) to construct a jsonb. + * + * If escontext points to an ErrorSaveContext, errors are reported there + * instead of being thrown. */ static inline Datum -jsonb_from_cstring(char *json, int len) +jsonb_from_cstring(char *json, int len, Node *escontext) { JsonLexContext *lex; JsonbInState state; @@ -263,6 +267,7 @@ jsonb_from_cstring(char *json, int len) memset(&sem, 0, sizeof(sem)); lex = makeJsonLexContextCstringLen(json, len, GetDatabaseEncoding(), true); + state.escontext = escontext; sem.semstate = (void *) &state; sem.object_start = jsonb_in_object_start; @@ -272,58 +277,67 @@ jsonb_from_cstring(char *json, int len) sem.scalar = jsonb_in_scalar; sem.object_field_start = jsonb_in_object_field_start; - pg_parse_json_or_ereport(lex, &sem); + if (!pg_parse_json_or_errsave(lex, &sem, escontext)) + return (Datum) 0; /* after parsing, the item member has the composed jsonb structure */ PG_RETURN_POINTER(JsonbValueToJsonb(state.res)); } -static size_t -checkStringLen(size_t len) +static bool +checkStringLen(size_t len, Node *escontext) { if (len > JENTRY_OFFLENMASK) - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("string too long to represent as jsonb string"), errdetail("Due to an implementation restriction, jsonb strings cannot exceed %d bytes.", JENTRY_OFFLENMASK))); - return len; + return true; } -static void +static JsonParseErrorType jsonb_in_object_start(void *pstate) { JsonbInState *_state = (JsonbInState *) pstate; _state->res = pushJsonbValue(&_state->parseState, WJB_BEGIN_OBJECT, NULL); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType jsonb_in_object_end(void *pstate) { JsonbInState *_state = (JsonbInState *) pstate; _state->res = pushJsonbValue(&_state->parseState, WJB_END_OBJECT, NULL); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType jsonb_in_array_start(void *pstate) { JsonbInState *_state = (JsonbInState *) pstate; _state->res = pushJsonbValue(&_state->parseState, WJB_BEGIN_ARRAY, NULL); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType jsonb_in_array_end(void *pstate) { JsonbInState *_state = (JsonbInState *) pstate; _state->res = pushJsonbValue(&_state->parseState, WJB_END_ARRAY, NULL); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType jsonb_in_object_field_start(void *pstate, char *fname, bool isnull) { JsonbInState *_state = (JsonbInState *) pstate; @@ -331,10 +345,14 @@ jsonb_in_object_field_start(void *pstate, char *fname, bool isnull) Assert(fname != NULL); v.type = jbvString; - v.val.string.len = checkStringLen(strlen(fname)); + v.val.string.len = strlen(fname); + if (!checkStringLen(v.val.string.len, _state->escontext)) + return JSON_SEM_ACTION_FAILED; v.val.string.val = fname; _state->res = pushJsonbValue(&_state->parseState, WJB_KEY, &v); + + return JSON_SUCCESS; } static void @@ -367,7 +385,7 @@ jsonb_put_escaped_value(StringInfo out, JsonbValue *scalarVal) /* * For jsonb we always want the de-escaped value - that's what's in token */ -static void +static JsonParseErrorType jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype) { JsonbInState *_state = (JsonbInState *) pstate; @@ -380,7 +398,9 @@ jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype) case JSON_TOKEN_STRING: Assert(token != NULL); v.type = jbvString; - v.val.string.len = checkStringLen(strlen(token)); + v.val.string.len = strlen(token); + if (!checkStringLen(v.val.string.len, _state->escontext)) + return JSON_SEM_ACTION_FAILED; v.val.string.val = token; break; case JSON_TOKEN_NUMBER: @@ -391,10 +411,11 @@ jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype) */ Assert(token != NULL); v.type = jbvNumeric; - numd = DirectFunctionCall3(numeric_in, - CStringGetDatum(token), - ObjectIdGetDatum(InvalidOid), - Int32GetDatum(-1)); + if (!DirectInputFunctionCallSafe(numeric_in, token, + InvalidOid, -1, + _state->escontext, + &numd)) + return JSON_SEM_ACTION_FAILED; v.val.numeric = DatumGetNumeric(numd); break; case JSON_TOKEN_TRUE: @@ -443,6 +464,8 @@ jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype) elog(ERROR, "unexpected parent of nested structure"); } } + + return JSON_SUCCESS; } /* @@ -726,6 +749,9 @@ jsonb_categorize_type(Oid typoid, * * If key_scalar is true, the value is stored as a key, so insist * it's of an acceptable type, and force it to be a jbvString. + * + * Note: currently, we assume that result->escontext is NULL and errors + * will be thrown. */ static void datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, @@ -898,7 +924,8 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, default: outputstr = OidOutputFunctionCall(outfuncoid, val); jb.type = jbvString; - jb.val.string.len = checkStringLen(strlen(outputstr)); + jb.val.string.len = strlen(outputstr); + (void) checkStringLen(jb.val.string.len, NULL); jb.val.string.val = outputstr; break; } @@ -1636,6 +1663,7 @@ jsonb_agg_finalfn(PG_FUNCTION_ARGS) * shallow clone is sufficient as we aren't going to change any of the * values, just add the final array end marker. */ + memset(&result, 0, sizeof(JsonbInState)); result.parseState = clone_parse_state(arg->res->parseState); @@ -1868,6 +1896,7 @@ jsonb_object_agg_finalfn(PG_FUNCTION_ARGS) * going to change any of the values, just add the final object end * marker. */ + memset(&result, 0, sizeof(JsonbInState)); result.parseState = clone_parse_state(arg->res->parseState); diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index bfc3f02a86..463a8fdf23 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -25,6 +25,7 @@ #include "lib/stringinfo.h" #include "mb/pg_wchar.h" #include "miscadmin.h" +#include "nodes/miscnodes.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/fmgroids.h" @@ -336,20 +337,20 @@ typedef struct JsObject static int report_json_context(JsonLexContext *lex); /* semantic action functions for json_object_keys */ -static void okeys_object_field_start(void *state, char *fname, bool isnull); -static void okeys_array_start(void *state); -static void okeys_scalar(void *state, char *token, JsonTokenType tokentype); +static JsonParseErrorType okeys_object_field_start(void *state, char *fname, bool isnull); +static JsonParseErrorType okeys_array_start(void *state); +static JsonParseErrorType okeys_scalar(void *state, char *token, JsonTokenType tokentype); /* semantic action functions for json_get* functions */ -static void get_object_start(void *state); -static void get_object_end(void *state); -static void get_object_field_start(void *state, char *fname, bool isnull); -static void get_object_field_end(void *state, char *fname, bool isnull); -static void get_array_start(void *state); -static void get_array_end(void *state); -static void get_array_element_start(void *state, bool isnull); -static void get_array_element_end(void *state, bool isnull); -static void get_scalar(void *state, char *token, JsonTokenType tokentype); +static JsonParseErrorType get_object_start(void *state); +static JsonParseErrorType get_object_end(void *state); +static JsonParseErrorType get_object_field_start(void *state, char *fname, bool isnull); +static JsonParseErrorType get_object_field_end(void *state, char *fname, bool isnull); +static JsonParseErrorType get_array_start(void *state); +static JsonParseErrorType get_array_end(void *state); +static JsonParseErrorType get_array_element_start(void *state, bool isnull); +static JsonParseErrorType get_array_element_end(void *state, bool isnull); +static JsonParseErrorType get_scalar(void *state, char *token, JsonTokenType tokentype); /* common worker function for json getter functions */ static Datum get_path_all(FunctionCallInfo fcinfo, bool as_text); @@ -359,9 +360,9 @@ static Datum get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text); static text *JsonbValueAsText(JsonbValue *v); /* semantic action functions for json_array_length */ -static void alen_object_start(void *state); -static void alen_scalar(void *state, char *token, JsonTokenType tokentype); -static void alen_array_element_start(void *state, bool isnull); +static JsonParseErrorType alen_object_start(void *state); +static JsonParseErrorType alen_scalar(void *state, char *token, JsonTokenType tokentype); +static JsonParseErrorType alen_array_element_start(void *state, bool isnull); /* common workers for json{b}_each* functions */ static Datum each_worker(FunctionCallInfo fcinfo, bool as_text); @@ -369,10 +370,10 @@ static Datum each_worker_jsonb(FunctionCallInfo fcinfo, const char *funcname, bool as_text); /* semantic action functions for json_each */ -static void each_object_field_start(void *state, char *fname, bool isnull); -static void each_object_field_end(void *state, char *fname, bool isnull); -static void each_array_start(void *state); -static void each_scalar(void *state, char *token, JsonTokenType tokentype); +static JsonParseErrorType each_object_field_start(void *state, char *fname, bool isnull); +static JsonParseErrorType each_object_field_end(void *state, char *fname, bool isnull); +static JsonParseErrorType each_array_start(void *state); +static JsonParseErrorType each_scalar(void *state, char *token, JsonTokenType tokentype); /* common workers for json{b}_array_elements_* functions */ static Datum elements_worker(FunctionCallInfo fcinfo, const char *funcname, @@ -381,44 +382,44 @@ static Datum elements_worker_jsonb(FunctionCallInfo fcinfo, const char *funcname bool as_text); /* semantic action functions for json_array_elements */ -static void elements_object_start(void *state); -static void elements_array_element_start(void *state, bool isnull); -static void elements_array_element_end(void *state, bool isnull); -static void elements_scalar(void *state, char *token, JsonTokenType tokentype); +static JsonParseErrorType elements_object_start(void *state); +static JsonParseErrorType elements_array_element_start(void *state, bool isnull); +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); /* semantic actions for populate_array_json */ -static void populate_array_object_start(void *_state); -static void populate_array_array_end(void *_state); -static void populate_array_element_start(void *_state, bool isnull); -static void populate_array_element_end(void *_state, bool isnull); -static void populate_array_scalar(void *_state, char *token, JsonTokenType tokentype); +static JsonParseErrorType populate_array_object_start(void *_state); +static JsonParseErrorType populate_array_array_end(void *_state); +static JsonParseErrorType populate_array_element_start(void *_state, bool isnull); +static JsonParseErrorType populate_array_element_end(void *_state, bool isnull); +static JsonParseErrorType populate_array_scalar(void *_state, char *token, JsonTokenType tokentype); /* semantic action functions for get_json_object_as_hash */ -static void hash_object_field_start(void *state, char *fname, bool isnull); -static void hash_object_field_end(void *state, char *fname, bool isnull); -static void hash_array_start(void *state); -static void hash_scalar(void *state, char *token, JsonTokenType tokentype); +static JsonParseErrorType hash_object_field_start(void *state, char *fname, bool isnull); +static JsonParseErrorType hash_object_field_end(void *state, char *fname, bool isnull); +static JsonParseErrorType hash_array_start(void *state); +static JsonParseErrorType hash_scalar(void *state, char *token, JsonTokenType tokentype); /* semantic action functions for populate_recordset */ -static void populate_recordset_object_field_start(void *state, char *fname, bool isnull); -static void populate_recordset_object_field_end(void *state, char *fname, bool isnull); -static void populate_recordset_scalar(void *state, char *token, JsonTokenType tokentype); -static void populate_recordset_object_start(void *state); -static void populate_recordset_object_end(void *state); -static void populate_recordset_array_start(void *state); -static void populate_recordset_array_element_start(void *state, bool isnull); +static JsonParseErrorType populate_recordset_object_field_start(void *state, char *fname, bool isnull); +static JsonParseErrorType populate_recordset_object_field_end(void *state, char *fname, bool isnull); +static JsonParseErrorType populate_recordset_scalar(void *state, char *token, JsonTokenType tokentype); +static JsonParseErrorType populate_recordset_object_start(void *state); +static JsonParseErrorType populate_recordset_object_end(void *state); +static JsonParseErrorType populate_recordset_array_start(void *state); +static JsonParseErrorType populate_recordset_array_element_start(void *state, bool isnull); /* semantic action functions for json_strip_nulls */ -static void sn_object_start(void *state); -static void sn_object_end(void *state); -static void sn_array_start(void *state); -static void sn_array_end(void *state); -static void sn_object_field_start(void *state, char *fname, bool isnull); -static void sn_array_element_start(void *state, bool isnull); -static void sn_scalar(void *state, char *token, JsonTokenType tokentype); +static JsonParseErrorType sn_object_start(void *state); +static JsonParseErrorType sn_object_end(void *state); +static JsonParseErrorType sn_array_start(void *state); +static JsonParseErrorType sn_array_end(void *state); +static JsonParseErrorType sn_object_field_start(void *state, char *fname, bool isnull); +static JsonParseErrorType sn_array_element_start(void *state, bool isnull); +static JsonParseErrorType sn_scalar(void *state, char *token, JsonTokenType tokentype); /* worker functions for populate_record, to_record, populate_recordset and to_recordset */ static Datum populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname, @@ -478,33 +479,43 @@ static void setPathArray(JsonbIterator **it, Datum *path_elems, JsonbValue *newval, uint32 nelems, int op_type); /* function supporting iterate_json_values */ -static void iterate_values_scalar(void *state, char *token, JsonTokenType tokentype); -static void iterate_values_object_field_start(void *state, char *fname, bool isnull); +static JsonParseErrorType iterate_values_scalar(void *state, char *token, JsonTokenType tokentype); +static JsonParseErrorType iterate_values_object_field_start(void *state, char *fname, bool isnull); /* functions supporting transform_json_string_values */ -static void transform_string_values_object_start(void *state); -static void transform_string_values_object_end(void *state); -static void transform_string_values_array_start(void *state); -static void transform_string_values_array_end(void *state); -static void transform_string_values_object_field_start(void *state, char *fname, bool isnull); -static void transform_string_values_array_element_start(void *state, bool isnull); -static void transform_string_values_scalar(void *state, char *token, JsonTokenType tokentype); +static JsonParseErrorType transform_string_values_object_start(void *state); +static JsonParseErrorType transform_string_values_object_end(void *state); +static JsonParseErrorType transform_string_values_array_start(void *state); +static JsonParseErrorType transform_string_values_array_end(void *state); +static JsonParseErrorType transform_string_values_object_field_start(void *state, char *fname, bool isnull); +static JsonParseErrorType transform_string_values_array_element_start(void *state, bool isnull); +static JsonParseErrorType transform_string_values_scalar(void *state, char *token, JsonTokenType tokentype); + /* - * pg_parse_json_or_ereport + * pg_parse_json_or_errsave * * This function is like pg_parse_json, except that it does not return a * JsonParseErrorType. Instead, in case of any failure, this function will + * save error data into *escontext if that's an ErrorSaveContext, otherwise * ereport(ERROR). + * + * Returns a boolean indicating success or failure (failure will only be + * returned when escontext is an ErrorSaveContext). */ -void -pg_parse_json_or_ereport(JsonLexContext *lex, JsonSemAction *sem) +bool +pg_parse_json_or_errsave(JsonLexContext *lex, JsonSemAction *sem, + Node *escontext) { JsonParseErrorType result; result = pg_parse_json(lex, sem); if (result != JSON_SUCCESS) - json_ereport_error(result, lex); + { + json_errsave_error(result, lex, escontext); + return false; + } + return true; } /* @@ -608,17 +619,24 @@ jsonb_object_keys(PG_FUNCTION_ARGS) * Report a JSON error. */ void -json_ereport_error(JsonParseErrorType error, JsonLexContext *lex) +json_errsave_error(JsonParseErrorType error, JsonLexContext *lex, + Node *escontext) { if (error == JSON_UNICODE_HIGH_ESCAPE || error == JSON_UNICODE_CODE_POINT_ZERO) - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_UNTRANSLATABLE_CHARACTER), errmsg("unsupported Unicode escape sequence"), errdetail_internal("%s", json_errdetail(error, lex)), report_json_context(lex))); + else if (error == JSON_SEM_ACTION_FAILED) + { + /* semantic action function had better have reported something */ + if (!SOFT_ERROR_OCCURRED(escontext)) + elog(ERROR, "JSON semantic action function did not provide error information"); + } else - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s", "json"), errdetail_internal("%s", json_errdetail(error, lex)), @@ -745,14 +763,14 @@ json_object_keys(PG_FUNCTION_ARGS) SRF_RETURN_DONE(funcctx); } -static void +static JsonParseErrorType okeys_object_field_start(void *state, char *fname, bool isnull) { OkeysState *_state = (OkeysState *) state; /* only collecting keys for the top level object */ if (_state->lex->lex_level != 1) - return; + return JSON_SUCCESS; /* enlarge result array if necessary */ if (_state->result_count >= _state->result_size) @@ -764,9 +782,11 @@ okeys_object_field_start(void *state, char *fname, bool isnull) /* save a copy of the field name */ _state->result[_state->result_count++] = pstrdup(fname); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType okeys_array_start(void *state) { OkeysState *_state = (OkeysState *) state; @@ -777,9 +797,11 @@ okeys_array_start(void *state) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot call %s on an array", "json_object_keys"))); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType okeys_scalar(void *state, char *token, JsonTokenType tokentype) { OkeysState *_state = (OkeysState *) state; @@ -790,6 +812,8 @@ okeys_scalar(void *state, char *token, JsonTokenType tokentype) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot call %s on a scalar", "json_object_keys"))); + + return JSON_SUCCESS; } /* @@ -1112,7 +1136,7 @@ get_worker(text *json, return state->tresult; } -static void +static JsonParseErrorType get_object_start(void *state) { GetState *_state = (GetState *) state; @@ -1127,9 +1151,11 @@ get_object_start(void *state) */ _state->result_start = _state->lex->token_start; } + + return JSON_SUCCESS; } -static void +static JsonParseErrorType get_object_end(void *state) { GetState *_state = (GetState *) state; @@ -1143,9 +1169,11 @@ get_object_end(void *state) _state->tresult = cstring_to_text_with_len(start, len); } + + return JSON_SUCCESS; } -static void +static JsonParseErrorType get_object_field_start(void *state, char *fname, bool isnull) { GetState *_state = (GetState *) state; @@ -1188,9 +1216,11 @@ get_object_field_start(void *state, char *fname, bool isnull) _state->result_start = _state->lex->token_start; } } + + return JSON_SUCCESS; } -static void +static JsonParseErrorType get_object_field_end(void *state, char *fname, bool isnull) { GetState *_state = (GetState *) state; @@ -1237,9 +1267,11 @@ get_object_field_end(void *state, char *fname, bool isnull) /* this should be unnecessary but let's do it for cleanliness: */ _state->result_start = NULL; } + + return JSON_SUCCESS; } -static void +static JsonParseErrorType get_array_start(void *state) { GetState *_state = (GetState *) state; @@ -1260,7 +1292,7 @@ get_array_start(void *state) error = json_count_array_elements(_state->lex, &nelements); if (error != JSON_SUCCESS) - json_ereport_error(error, _state->lex); + json_errsave_error(error, _state->lex, NULL); if (-_state->path_indexes[lex_level] <= nelements) _state->path_indexes[lex_level] += nelements; @@ -1275,9 +1307,11 @@ get_array_start(void *state) */ _state->result_start = _state->lex->token_start; } + + return JSON_SUCCESS; } -static void +static JsonParseErrorType get_array_end(void *state) { GetState *_state = (GetState *) state; @@ -1291,9 +1325,11 @@ get_array_end(void *state) _state->tresult = cstring_to_text_with_len(start, len); } + + return JSON_SUCCESS; } -static void +static JsonParseErrorType get_array_element_start(void *state, bool isnull) { GetState *_state = (GetState *) state; @@ -1337,9 +1373,11 @@ get_array_element_start(void *state, bool isnull) _state->result_start = _state->lex->token_start; } } + + return JSON_SUCCESS; } -static void +static JsonParseErrorType get_array_element_end(void *state, bool isnull) { GetState *_state = (GetState *) state; @@ -1379,9 +1417,11 @@ get_array_element_end(void *state, bool isnull) _state->result_start = NULL; } + + return JSON_SUCCESS; } -static void +static JsonParseErrorType get_scalar(void *state, char *token, JsonTokenType tokentype) { GetState *_state = (GetState *) state; @@ -1420,6 +1460,8 @@ get_scalar(void *state, char *token, JsonTokenType tokentype) /* make sure the next call to get_scalar doesn't overwrite it */ _state->next_scalar = false; } + + return JSON_SUCCESS; } Datum @@ -1834,7 +1876,7 @@ jsonb_array_length(PG_FUNCTION_ARGS) * a scalar or an object). */ -static void +static JsonParseErrorType alen_object_start(void *state) { AlenState *_state = (AlenState *) state; @@ -1844,9 +1886,11 @@ alen_object_start(void *state) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot get array length of a non-array"))); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType alen_scalar(void *state, char *token, JsonTokenType tokentype) { AlenState *_state = (AlenState *) state; @@ -1856,9 +1900,11 @@ alen_scalar(void *state, char *token, JsonTokenType tokentype) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot get array length of a scalar"))); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType alen_array_element_start(void *state, bool isnull) { AlenState *_state = (AlenState *) state; @@ -1866,6 +1912,8 @@ alen_array_element_start(void *state, bool isnull) /* just count up all the level 1 elements */ if (_state->lex->lex_level == 1) _state->count++; + + return JSON_SUCCESS; } /* @@ -2026,7 +2074,7 @@ each_worker(FunctionCallInfo fcinfo, bool as_text) } -static void +static JsonParseErrorType each_object_field_start(void *state, char *fname, bool isnull) { EachState *_state = (EachState *) state; @@ -2044,9 +2092,11 @@ each_object_field_start(void *state, char *fname, bool isnull) else _state->result_start = _state->lex->token_start; } + + return JSON_SUCCESS; } -static void +static JsonParseErrorType each_object_field_end(void *state, char *fname, bool isnull) { EachState *_state = (EachState *) state; @@ -2059,7 +2109,7 @@ each_object_field_end(void *state, char *fname, bool isnull) /* skip over nested objects */ if (_state->lex->lex_level != 1) - return; + return JSON_SUCCESS; /* use the tmp context so we can clean up after each tuple is done */ old_cxt = MemoryContextSwitchTo(_state->tmp_cxt); @@ -2090,9 +2140,11 @@ each_object_field_end(void *state, char *fname, bool isnull) /* clean up and switch back */ MemoryContextSwitchTo(old_cxt); MemoryContextReset(_state->tmp_cxt); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType each_array_start(void *state) { EachState *_state = (EachState *) state; @@ -2102,9 +2154,11 @@ each_array_start(void *state) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot deconstruct an array as an object"))); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType each_scalar(void *state, char *token, JsonTokenType tokentype) { EachState *_state = (EachState *) state; @@ -2118,6 +2172,8 @@ each_scalar(void *state, char *token, JsonTokenType tokentype) /* supply de-escaped value if required */ if (_state->next_scalar) _state->normalized_scalar = token; + + return JSON_SUCCESS; } /* @@ -2268,7 +2324,7 @@ elements_worker(FunctionCallInfo fcinfo, const char *funcname, bool as_text) PG_RETURN_NULL(); } -static void +static JsonParseErrorType elements_array_element_start(void *state, bool isnull) { ElementsState *_state = (ElementsState *) state; @@ -2286,9 +2342,11 @@ elements_array_element_start(void *state, bool isnull) else _state->result_start = _state->lex->token_start; } + + return JSON_SUCCESS; } -static void +static JsonParseErrorType elements_array_element_end(void *state, bool isnull) { ElementsState *_state = (ElementsState *) state; @@ -2301,7 +2359,7 @@ elements_array_element_end(void *state, bool isnull) /* skip over nested objects */ if (_state->lex->lex_level != 1) - return; + return JSON_SUCCESS; /* use the tmp context so we can clean up after each tuple is done */ old_cxt = MemoryContextSwitchTo(_state->tmp_cxt); @@ -2330,9 +2388,11 @@ elements_array_element_end(void *state, bool isnull) /* clean up and switch back */ MemoryContextSwitchTo(old_cxt); MemoryContextReset(_state->tmp_cxt); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType elements_object_start(void *state) { ElementsState *_state = (ElementsState *) state; @@ -2343,9 +2403,11 @@ elements_object_start(void *state) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot call %s on a non-array", _state->function_name))); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType elements_scalar(void *state, char *token, JsonTokenType tokentype) { ElementsState *_state = (ElementsState *) state; @@ -2360,6 +2422,8 @@ elements_scalar(void *state, char *token, JsonTokenType tokentype) /* supply de-escaped value if required */ if (_state->next_scalar) _state->normalized_scalar = token; + + return JSON_SUCCESS; } /* @@ -2508,7 +2572,7 @@ populate_array_element(PopulateArrayContext *ctx, int ndim, JsValue *jsv) } /* json object start handler for populate_array_json() */ -static void +static JsonParseErrorType populate_array_object_start(void *_state) { PopulateArrayState *state = (PopulateArrayState *) _state; @@ -2518,10 +2582,12 @@ populate_array_object_start(void *_state) populate_array_assign_ndims(state->ctx, ndim); else if (ndim < state->ctx->ndims) populate_array_report_expected_array(state->ctx, ndim); + + return JSON_SUCCESS; } /* json array end handler for populate_array_json() */ -static void +static JsonParseErrorType populate_array_array_end(void *_state) { PopulateArrayState *state = (PopulateArrayState *) _state; @@ -2533,10 +2599,12 @@ populate_array_array_end(void *_state) if (ndim < ctx->ndims) populate_array_check_dimension(ctx, ndim); + + return JSON_SUCCESS; } /* json array element start handler for populate_array_json() */ -static void +static JsonParseErrorType populate_array_element_start(void *_state, bool isnull) { PopulateArrayState *state = (PopulateArrayState *) _state; @@ -2549,10 +2617,12 @@ populate_array_element_start(void *_state, bool isnull) state->element_type = state->lex->token_type; state->element_scalar = NULL; } + + return JSON_SUCCESS; } /* json array element end handler for populate_array_json() */ -static void +static JsonParseErrorType populate_array_element_end(void *_state, bool isnull) { PopulateArrayState *state = (PopulateArrayState *) _state; @@ -2588,10 +2658,12 @@ populate_array_element_end(void *_state, bool isnull) populate_array_element(ctx, ndim, &jsv); } + + return JSON_SUCCESS; } /* json scalar handler for populate_array_json() */ -static void +static JsonParseErrorType populate_array_scalar(void *_state, char *token, JsonTokenType tokentype) { PopulateArrayState *state = (PopulateArrayState *) _state; @@ -2610,6 +2682,8 @@ populate_array_scalar(void *_state, char *token, JsonTokenType tokentype) /* element_type must already be set in populate_array_element_start() */ Assert(state->element_type == tokentype); } + + return JSON_SUCCESS; } /* parse a json array and populate array */ @@ -3491,13 +3565,13 @@ get_json_object_as_hash(char *json, int len, const char *funcname) return tab; } -static void +static JsonParseErrorType hash_object_field_start(void *state, char *fname, bool isnull) { JHashState *_state = (JHashState *) state; if (_state->lex->lex_level > 1) - return; + return JSON_SUCCESS; /* remember token type */ _state->saved_token_type = _state->lex->token_type; @@ -3513,9 +3587,11 @@ hash_object_field_start(void *state, char *fname, bool isnull) /* must be a scalar */ _state->save_json_start = NULL; } + + return JSON_SUCCESS; } -static void +static JsonParseErrorType hash_object_field_end(void *state, char *fname, bool isnull) { JHashState *_state = (JHashState *) state; @@ -3526,7 +3602,7 @@ hash_object_field_end(void *state, char *fname, bool isnull) * Ignore nested fields. */ if (_state->lex->lex_level > 1) - return; + return JSON_SUCCESS; /* * Ignore field names >= NAMEDATALEN - they can't match a record field. @@ -3536,7 +3612,7 @@ hash_object_field_end(void *state, char *fname, bool isnull) * has previously insisted on exact equality, so we keep this behavior.) */ if (strlen(fname) >= NAMEDATALEN) - return; + return JSON_SUCCESS; hashentry = hash_search(_state->hash, fname, HASH_ENTER, &found); @@ -3562,9 +3638,11 @@ hash_object_field_end(void *state, char *fname, bool isnull) /* must have had a scalar instead */ hashentry->val = _state->saved_scalar; } + + return JSON_SUCCESS; } -static void +static JsonParseErrorType hash_array_start(void *state) { JHashState *_state = (JHashState *) state; @@ -3573,9 +3651,11 @@ hash_array_start(void *state) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot call %s on an array", _state->function_name))); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType hash_scalar(void *state, char *token, JsonTokenType tokentype) { JHashState *_state = (JHashState *) state; @@ -3591,6 +3671,8 @@ hash_scalar(void *state, char *token, JsonTokenType tokentype) /* saved_token_type must already be set in hash_object_field_start() */ Assert(_state->saved_token_type == tokentype); } + + return JSON_SUCCESS; } @@ -3840,7 +3922,7 @@ populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname, PG_RETURN_NULL(); } -static void +static JsonParseErrorType populate_recordset_object_start(void *state) { PopulateRecordsetState *_state = (PopulateRecordsetState *) state; @@ -3856,7 +3938,7 @@ populate_recordset_object_start(void *state) /* Nested objects require no special processing */ if (lex_level > 1) - return; + return JSON_SUCCESS; /* Object at level 1: set up a new hash table for this object */ ctl.keysize = NAMEDATALEN; @@ -3866,9 +3948,11 @@ populate_recordset_object_start(void *state) 100, &ctl, HASH_ELEM | HASH_STRINGS | HASH_CONTEXT); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType populate_recordset_object_end(void *state) { PopulateRecordsetState *_state = (PopulateRecordsetState *) state; @@ -3876,7 +3960,7 @@ populate_recordset_object_end(void *state) /* Nested objects require no special processing */ if (_state->lex->lex_level > 1) - return; + return JSON_SUCCESS; obj.is_json = true; obj.val.json_hash = _state->json_hash; @@ -3887,9 +3971,11 @@ populate_recordset_object_end(void *state) /* Done with hash for this object */ hash_destroy(_state->json_hash); _state->json_hash = NULL; + + return JSON_SUCCESS; } -static void +static JsonParseErrorType populate_recordset_array_element_start(void *state, bool isnull) { PopulateRecordsetState *_state = (PopulateRecordsetState *) state; @@ -3900,15 +3986,18 @@ populate_recordset_array_element_start(void *state, bool isnull) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("argument of %s must be an array of objects", _state->function_name))); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType populate_recordset_array_start(void *state) { /* nothing to do */ + return JSON_SUCCESS; } -static void +static JsonParseErrorType populate_recordset_scalar(void *state, char *token, JsonTokenType tokentype) { PopulateRecordsetState *_state = (PopulateRecordsetState *) state; @@ -3921,15 +4010,17 @@ populate_recordset_scalar(void *state, char *token, JsonTokenType tokentype) if (_state->lex->lex_level == 2) _state->saved_scalar = token; + + return JSON_SUCCESS; } -static void +static JsonParseErrorType populate_recordset_object_field_start(void *state, char *fname, bool isnull) { PopulateRecordsetState *_state = (PopulateRecordsetState *) state; if (_state->lex->lex_level > 2) - return; + return JSON_SUCCESS; _state->saved_token_type = _state->lex->token_type; @@ -3942,9 +4033,11 @@ populate_recordset_object_field_start(void *state, char *fname, bool isnull) { _state->save_json_start = NULL; } + + return JSON_SUCCESS; } -static void +static JsonParseErrorType populate_recordset_object_field_end(void *state, char *fname, bool isnull) { PopulateRecordsetState *_state = (PopulateRecordsetState *) state; @@ -3955,7 +4048,7 @@ populate_recordset_object_field_end(void *state, char *fname, bool isnull) * Ignore nested fields. */ if (_state->lex->lex_level > 2) - return; + return JSON_SUCCESS; /* * Ignore field names >= NAMEDATALEN - they can't match a record field. @@ -3965,7 +4058,7 @@ populate_recordset_object_field_end(void *state, char *fname, bool isnull) * has previously insisted on exact equality, so we keep this behavior.) */ if (strlen(fname) >= NAMEDATALEN) - return; + return JSON_SUCCESS; hashentry = hash_search(_state->json_hash, fname, HASH_ENTER, &found); @@ -3991,6 +4084,8 @@ populate_recordset_object_field_end(void *state, char *fname, bool isnull) /* must have had a scalar instead */ hashentry->val = _state->saved_scalar; } + + return JSON_SUCCESS; } /* @@ -4002,39 +4097,47 @@ populate_recordset_object_field_end(void *state, char *fname, bool isnull) * is called. */ -static void +static JsonParseErrorType sn_object_start(void *state) { StripnullState *_state = (StripnullState *) state; appendStringInfoCharMacro(_state->strval, '{'); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType sn_object_end(void *state) { StripnullState *_state = (StripnullState *) state; appendStringInfoCharMacro(_state->strval, '}'); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType sn_array_start(void *state) { StripnullState *_state = (StripnullState *) state; appendStringInfoCharMacro(_state->strval, '['); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType sn_array_end(void *state) { StripnullState *_state = (StripnullState *) state; appendStringInfoCharMacro(_state->strval, ']'); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType sn_object_field_start(void *state, char *fname, bool isnull) { StripnullState *_state = (StripnullState *) state; @@ -4047,7 +4150,7 @@ sn_object_field_start(void *state, char *fname, bool isnull) * object or array. The flag will be reset in the scalar action. */ _state->skip_next_null = true; - return; + return JSON_SUCCESS; } if (_state->strval->data[_state->strval->len - 1] != '{') @@ -4060,18 +4163,22 @@ sn_object_field_start(void *state, char *fname, bool isnull) escape_json(_state->strval, fname); appendStringInfoCharMacro(_state->strval, ':'); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType sn_array_element_start(void *state, bool isnull) { StripnullState *_state = (StripnullState *) state; if (_state->strval->data[_state->strval->len - 1] != '[') appendStringInfoCharMacro(_state->strval, ','); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType sn_scalar(void *state, char *token, JsonTokenType tokentype) { StripnullState *_state = (StripnullState *) state; @@ -4080,13 +4187,15 @@ sn_scalar(void *state, char *token, JsonTokenType tokentype) { Assert(tokentype == JSON_TOKEN_NULL); _state->skip_next_null = false; - return; + return JSON_SUCCESS; } if (tokentype == JSON_TOKEN_STRING) escape_json(_state->strval, token); else appendStringInfoString(_state->strval, token); + + return JSON_SUCCESS; } /* @@ -5326,7 +5435,7 @@ iterate_json_values(text *json, uint32 flags, void *action_state, * An auxiliary function for iterate_json_values to invoke a specified * JsonIterateStringValuesAction for specified values. */ -static void +static JsonParseErrorType iterate_values_scalar(void *state, char *token, JsonTokenType tokentype) { IterateJsonStringValuesState *_state = (IterateJsonStringValuesState *) state; @@ -5350,9 +5459,11 @@ iterate_values_scalar(void *state, char *token, JsonTokenType tokentype) /* do not call callback for any other token */ break; } + + return JSON_SUCCESS; } -static void +static JsonParseErrorType iterate_values_object_field_start(void *state, char *fname, bool isnull) { IterateJsonStringValuesState *_state = (IterateJsonStringValuesState *) state; @@ -5363,6 +5474,8 @@ iterate_values_object_field_start(void *state, char *fname, bool isnull) _state->action(_state->action_state, val, strlen(val)); } + + return JSON_SUCCESS; } /* @@ -5430,7 +5543,6 @@ transform_json_string_values(text *json, void *action_state, state->action_state = action_state; sem->semstate = (void *) state; - sem->scalar = transform_string_values_scalar; sem->object_start = transform_string_values_object_start; sem->object_end = transform_string_values_object_end; sem->array_start = transform_string_values_array_start; @@ -5449,39 +5561,47 @@ transform_json_string_values(text *json, void *action_state, * specified JsonTransformStringValuesAction for all values and left everything * else untouched. */ -static void +static JsonParseErrorType transform_string_values_object_start(void *state) { TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state; appendStringInfoCharMacro(_state->strval, '{'); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType transform_string_values_object_end(void *state) { TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state; appendStringInfoCharMacro(_state->strval, '}'); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType transform_string_values_array_start(void *state) { TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state; appendStringInfoCharMacro(_state->strval, '['); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType transform_string_values_array_end(void *state) { TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state; appendStringInfoCharMacro(_state->strval, ']'); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType transform_string_values_object_field_start(void *state, char *fname, bool isnull) { TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state; @@ -5495,18 +5615,22 @@ transform_string_values_object_field_start(void *state, char *fname, bool isnull */ escape_json(_state->strval, fname); appendStringInfoCharMacro(_state->strval, ':'); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType transform_string_values_array_element_start(void *state, bool isnull) { TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state; if (_state->strval->data[_state->strval->len - 1] != '[') appendStringInfoCharMacro(_state->strval, ','); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType transform_string_values_scalar(void *state, char *token, JsonTokenType tokentype) { TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state; @@ -5519,4 +5643,6 @@ transform_string_values_scalar(void *state, char *token, JsonTokenType tokentype } else appendStringInfoString(_state->strval, token); + + return JSON_SUCCESS; } diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c index 0d37f69298..7b28a266ce 100644 --- a/src/backend/utils/fmgr/fmgr.c +++ b/src/backend/utils/fmgr/fmgr.c @@ -1614,6 +1614,51 @@ InputFunctionCallSafe(FmgrInfo *flinfo, char *str, return true; } +/* + * Call a directly-named datatype input function, with non-exception + * handling of "soft" errors. + * + * This is like InputFunctionCallSafe, except that it is given a direct + * pointer to the C function to call. We assume that that function is + * strict. Also, the function cannot be one that needs to + * look at FmgrInfo, since there won't be any. + */ +bool +DirectInputFunctionCallSafe(PGFunction func, char *str, + Oid typioparam, int32 typmod, + fmNodePtr escontext, + Datum *result) +{ + LOCAL_FCINFO(fcinfo, 3); + + if (str == NULL) + { + *result = (Datum) 0; /* just return null result */ + return true; + } + + InitFunctionCallInfoData(*fcinfo, NULL, 3, InvalidOid, escontext, NULL); + + fcinfo->args[0].value = CStringGetDatum(str); + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = ObjectIdGetDatum(typioparam); + fcinfo->args[1].isnull = false; + fcinfo->args[2].value = Int32GetDatum(typmod); + fcinfo->args[2].isnull = false; + + *result = (*func) (fcinfo); + + /* Result value is garbage, and could be null, if an error was reported */ + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + + /* Otherwise, shouldn't get null result */ + if (fcinfo->isnull) + elog(ERROR, "input function %p returned NULL", (void *) func); + + return true; +} + /* * Call a previously-looked-up datatype output function. * diff --git a/src/bin/pg_verifybackup/parse_manifest.c b/src/bin/pg_verifybackup/parse_manifest.c index 6364b01282..beff018e18 100644 --- a/src/bin/pg_verifybackup/parse_manifest.c +++ b/src/bin/pg_verifybackup/parse_manifest.c @@ -88,14 +88,14 @@ typedef struct char *manifest_checksum; } JsonManifestParseState; -static void json_manifest_object_start(void *state); -static void json_manifest_object_end(void *state); -static void json_manifest_array_start(void *state); -static void json_manifest_array_end(void *state); -static void json_manifest_object_field_start(void *state, char *fname, - bool isnull); -static void json_manifest_scalar(void *state, char *token, - JsonTokenType tokentype); +static JsonParseErrorType json_manifest_object_start(void *state); +static JsonParseErrorType json_manifest_object_end(void *state); +static JsonParseErrorType json_manifest_array_start(void *state); +static JsonParseErrorType json_manifest_array_end(void *state); +static JsonParseErrorType json_manifest_object_field_start(void *state, char *fname, + bool isnull); +static JsonParseErrorType json_manifest_scalar(void *state, char *token, + JsonTokenType tokentype); static void json_manifest_finalize_file(JsonManifestParseState *parse); static void json_manifest_finalize_wal_range(JsonManifestParseState *parse); static void verify_manifest_checksum(JsonManifestParseState *parse, @@ -162,7 +162,7 @@ json_parse_manifest(JsonManifestParseContext *context, char *buffer, * WAL range is also expected to be an object. If we're anywhere else in the * document, it's an error. */ -static void +static JsonParseErrorType json_manifest_object_start(void *state) { JsonManifestParseState *parse = state; @@ -191,6 +191,8 @@ json_manifest_object_start(void *state) "unexpected object start"); break; } + + return JSON_SUCCESS; } /* @@ -201,7 +203,7 @@ json_manifest_object_start(void *state) * reach the end of an object representing a particular file or WAL range, * we must call json_manifest_finalize_file() to save the associated details. */ -static void +static JsonParseErrorType json_manifest_object_end(void *state) { JsonManifestParseState *parse = state; @@ -224,6 +226,8 @@ json_manifest_object_end(void *state) "unexpected object end"); break; } + + return JSON_SUCCESS; } /* @@ -233,7 +237,7 @@ json_manifest_object_end(void *state) * should be an array. Similarly for the "WAL-Ranges" key. No other arrays * are expected. */ -static void +static JsonParseErrorType json_manifest_array_start(void *state) { JsonManifestParseState *parse = state; @@ -251,6 +255,8 @@ json_manifest_array_start(void *state) "unexpected array start"); break; } + + return JSON_SUCCESS; } /* @@ -258,7 +264,7 @@ json_manifest_array_start(void *state) * * The cases here are analogous to those in json_manifest_array_start. */ -static void +static JsonParseErrorType json_manifest_array_end(void *state) { JsonManifestParseState *parse = state; @@ -274,12 +280,14 @@ json_manifest_array_end(void *state) "unexpected array end"); break; } + + return JSON_SUCCESS; } /* * Invoked at the start of each object field in the JSON document. */ -static void +static JsonParseErrorType json_manifest_object_field_start(void *state, char *fname, bool isnull) { JsonManifestParseState *parse = state; @@ -367,6 +375,8 @@ json_manifest_object_field_start(void *state, char *fname, bool isnull) "unexpected object field"); break; } + + return JSON_SUCCESS; } /* @@ -384,7 +394,7 @@ json_manifest_object_field_start(void *state, char *fname, bool isnull) * reach either the end of the object representing this file, or the end * of the manifest, as the case may be. */ -static void +static JsonParseErrorType json_manifest_scalar(void *state, char *token, JsonTokenType tokentype) { JsonManifestParseState *parse = state; @@ -448,6 +458,8 @@ json_manifest_scalar(void *state, char *token, JsonTokenType tokentype) json_manifest_parse_failure(parse->context, "unexpected scalar"); break; } + + return JSON_SUCCESS; } /* diff --git a/src/common/jsonapi.c b/src/common/jsonapi.c index 873357aa02..83c286b89b 100644 --- a/src/common/jsonapi.c +++ b/src/common/jsonapi.c @@ -298,9 +298,9 @@ parse_scalar(JsonLexContext *lex, JsonSemAction *sem) return result; /* invoke the callback */ - (*sfunc) (sem->semstate, val, tok); + result = (*sfunc) (sem->semstate, val, tok); - return JSON_SUCCESS; + return result; } static JsonParseErrorType @@ -335,7 +335,11 @@ parse_object_field(JsonLexContext *lex, JsonSemAction *sem) isnull = tok == JSON_TOKEN_NULL; if (ostart != NULL) - (*ostart) (sem->semstate, fname, isnull); + { + result = (*ostart) (sem->semstate, fname, isnull); + if (result != JSON_SUCCESS) + return result; + } switch (tok) { @@ -352,7 +356,12 @@ parse_object_field(JsonLexContext *lex, JsonSemAction *sem) return result; if (oend != NULL) - (*oend) (sem->semstate, fname, isnull); + { + result = (*oend) (sem->semstate, fname, isnull); + if (result != JSON_SUCCESS) + return result; + } + return JSON_SUCCESS; } @@ -373,7 +382,11 @@ parse_object(JsonLexContext *lex, JsonSemAction *sem) #endif if (ostart != NULL) - (*ostart) (sem->semstate); + { + result = (*ostart) (sem->semstate); + if (result != JSON_SUCCESS) + return result; + } /* * Data inside an object is at a higher nesting level than the object @@ -417,7 +430,11 @@ parse_object(JsonLexContext *lex, JsonSemAction *sem) lex->lex_level--; if (oend != NULL) - (*oend) (sem->semstate); + { + result = (*oend) (sem->semstate); + if (result != JSON_SUCCESS) + return result; + } return JSON_SUCCESS; } @@ -429,13 +446,16 @@ parse_array_element(JsonLexContext *lex, JsonSemAction *sem) json_aelem_action aend = sem->array_element_end; JsonTokenType tok = lex_peek(lex); JsonParseErrorType result; - bool isnull; isnull = tok == JSON_TOKEN_NULL; if (astart != NULL) - (*astart) (sem->semstate, isnull); + { + result = (*astart) (sem->semstate, isnull); + if (result != JSON_SUCCESS) + return result; + } /* an array element is any object, array or scalar */ switch (tok) @@ -454,7 +474,11 @@ parse_array_element(JsonLexContext *lex, JsonSemAction *sem) return result; if (aend != NULL) - (*aend) (sem->semstate, isnull); + { + result = (*aend) (sem->semstate, isnull); + if (result != JSON_SUCCESS) + return result; + } return JSON_SUCCESS; } @@ -475,7 +499,11 @@ parse_array(JsonLexContext *lex, JsonSemAction *sem) #endif if (astart != NULL) - (*astart) (sem->semstate); + { + result = (*astart) (sem->semstate); + if (result != JSON_SUCCESS) + return result; + } /* * Data inside an array is at a higher nesting level than the array @@ -508,7 +536,11 @@ parse_array(JsonLexContext *lex, JsonSemAction *sem) lex->lex_level--; if (aend != NULL) - (*aend) (sem->semstate); + { + result = (*aend) (sem->semstate); + if (result != JSON_SUCCESS) + return result; + } return JSON_SUCCESS; } @@ -1139,6 +1171,9 @@ json_errdetail(JsonParseErrorType error, JsonLexContext *lex) return _("Unicode high surrogate must not follow a high surrogate."); case JSON_UNICODE_LOW_SURROGATE: return _("Unicode low surrogate must follow a high surrogate."); + case JSON_SEM_ACTION_FAILED: + /* fall through to the error code after switch */ + break; } /* diff --git a/src/include/common/jsonapi.h b/src/include/common/jsonapi.h index 8d31630e5c..4590ff2476 100644 --- a/src/include/common/jsonapi.h +++ b/src/include/common/jsonapi.h @@ -52,7 +52,8 @@ typedef enum JSON_UNICODE_ESCAPE_FORMAT, JSON_UNICODE_HIGH_ESCAPE, JSON_UNICODE_HIGH_SURROGATE, - JSON_UNICODE_LOW_SURROGATE + JSON_UNICODE_LOW_SURROGATE, + JSON_SEM_ACTION_FAILED /* error should already be reported */ } JsonParseErrorType; @@ -84,14 +85,15 @@ typedef struct JsonLexContext StringInfo strval; } JsonLexContext; -typedef void (*json_struct_action) (void *state); -typedef void (*json_ofield_action) (void *state, char *fname, bool isnull); -typedef void (*json_aelem_action) (void *state, bool isnull); -typedef void (*json_scalar_action) (void *state, char *token, JsonTokenType tokentype); +typedef JsonParseErrorType (*json_struct_action) (void *state); +typedef JsonParseErrorType (*json_ofield_action) (void *state, char *fname, bool isnull); +typedef JsonParseErrorType (*json_aelem_action) (void *state, bool isnull); +typedef JsonParseErrorType (*json_scalar_action) (void *state, char *token, JsonTokenType tokentype); /* * Semantic Action structure for use in parsing json. + * * Any of these actions can be NULL, in which case nothing is done at that * point, Likewise, semstate can be NULL. Using an all-NULL structure amounts * to doing a pure parse with no side-effects, and is therefore exactly @@ -100,6 +102,11 @@ typedef void (*json_scalar_action) (void *state, char *token, JsonTokenType toke * The 'fname' and 'token' strings passed to these actions are palloc'd. * They are not free'd or used further by the parser, so the action function * is free to do what it wishes with them. + * + * All action functions return JsonParseErrorType. If the result isn't + * JSON_SUCCESS, the parse is abandoned and that error code is returned. + * If it is JSON_SEM_ACTION_FAILED, the action function is responsible + * for having reported the error in some appropriate way. */ typedef struct JsonSemAction { diff --git a/src/include/fmgr.h b/src/include/fmgr.h index b7832d0aa2..972afe3aff 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -704,6 +704,10 @@ extern bool InputFunctionCallSafe(FmgrInfo *flinfo, char *str, Oid typioparam, int32 typmod, fmNodePtr escontext, Datum *result); +extern bool DirectInputFunctionCallSafe(PGFunction func, char *str, + Oid typioparam, int32 typmod, + fmNodePtr escontext, + Datum *result); extern Datum OidInputFunctionCall(Oid functionId, char *str, Oid typioparam, int32 typmod); extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val); diff --git a/src/include/utils/jsonfuncs.h b/src/include/utils/jsonfuncs.h index 865b2ff7c1..7fad0269f6 100644 --- a/src/include/utils/jsonfuncs.h +++ b/src/include/utils/jsonfuncs.h @@ -39,11 +39,16 @@ typedef text *(*JsonTransformStringValuesAction) (void *state, char *elem_value, /* build a JsonLexContext from a text datum */ extern JsonLexContext *makeJsonLexContext(text *json, bool need_escapes); -/* try to parse json, and ereport(ERROR) on failure */ -extern void pg_parse_json_or_ereport(JsonLexContext *lex, JsonSemAction *sem); +/* try to parse json, and errsave(escontext) on failure */ +extern bool pg_parse_json_or_errsave(JsonLexContext *lex, JsonSemAction *sem, + struct Node *escontext); -/* report an error during json lexing or parsing */ -extern void json_ereport_error(JsonParseErrorType error, JsonLexContext *lex); +#define pg_parse_json_or_ereport(lex, sem) \ + (void) pg_parse_json_or_errsave(lex, sem, NULL) + +/* save an error during json lexing or parsing */ +extern void json_errsave_error(JsonParseErrorType error, JsonLexContext *lex, + struct Node *escontext); extern uint32 parse_jsonb_index_flags(Jsonb *jb); extern void iterate_jsonb_values(Jsonb *jb, uint32 flags, void *state, diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out index cb181226e9..af96ce4180 100644 --- a/src/test/regress/expected/json.out +++ b/src/test/regress/expected/json.out @@ -320,6 +320,25 @@ LINE 1: SELECT '{ DETAIL: Expected JSON value, but found "}". CONTEXT: JSON data, line 4: ...yveryveryveryveryveryveryveryverylongfieldname":} -- ERROR missing value for last field +-- test non-error-throwing input +select pg_input_is_valid('{"a":true}', 'json'); + pg_input_is_valid +------------------- + t +(1 row) + +select pg_input_is_valid('{"a":true', 'json'); + pg_input_is_valid +------------------- + f +(1 row) + +select pg_input_error_message('{"a":true', 'json'); + pg_input_error_message +------------------------------------ + invalid input syntax for type json +(1 row) + --constructors -- array_to_json SELECT array_to_json(array(select 1 as a)); diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out index b2b3677482..be85676b5b 100644 --- a/src/test/regress/expected/jsonb.out +++ b/src/test/regress/expected/jsonb.out @@ -310,6 +310,31 @@ LINE 1: SELECT '{ DETAIL: Expected JSON value, but found "}". CONTEXT: JSON data, line 4: ...yveryveryveryveryveryveryveryverylongfieldname":} -- ERROR missing value for last field +-- test non-error-throwing input +select pg_input_is_valid('{"a":true}', 'jsonb'); + pg_input_is_valid +------------------- + t +(1 row) + +select pg_input_is_valid('{"a":true', 'jsonb'); + pg_input_is_valid +------------------- + f +(1 row) + +select pg_input_error_message('{"a":true', 'jsonb'); + pg_input_error_message +------------------------------------ + invalid input syntax for type json +(1 row) + +select pg_input_error_message('{"a":1e1000000}', 'jsonb'); + pg_input_error_message +-------------------------------- + value overflows numeric format +(1 row) + -- make sure jsonb is passed through json generators without being escaped SELECT array_to_json(ARRAY [jsonb '{"a":1}', jsonb '{"b":[2,3]}']); array_to_json diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql index 589e0cea36..21534ed959 100644 --- a/src/test/regress/sql/json.sql +++ b/src/test/regress/sql/json.sql @@ -81,6 +81,11 @@ SELECT '{ "averyveryveryveryveryveryveryveryveryverylongfieldname":}'::json; -- ERROR missing value for last field +-- test non-error-throwing input +select pg_input_is_valid('{"a":true}', 'json'); +select pg_input_is_valid('{"a":true', 'json'); +select pg_input_error_message('{"a":true', 'json'); + --constructors -- array_to_json diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql index 8d25966267..bc44ad1518 100644 --- a/src/test/regress/sql/jsonb.sql +++ b/src/test/regress/sql/jsonb.sql @@ -86,6 +86,12 @@ SELECT '{ "averyveryveryveryveryveryveryveryveryverylongfieldname":}'::jsonb; -- ERROR missing value for last field +-- test non-error-throwing input +select pg_input_is_valid('{"a":true}', 'jsonb'); +select pg_input_is_valid('{"a":true', 'jsonb'); +select pg_input_error_message('{"a":true', 'jsonb'); +select pg_input_error_message('{"a":1e1000000}', 'jsonb'); + -- make sure jsonb is passed through json generators without being escaped SELECT array_to_json(ARRAY [jsonb '{"a":1}', jsonb '{"b":[2,3]}']);
pgsql-hackers by date: