diff options
author | Robert Haas | 2020-01-27 16:03:21 +0000 |
---|---|---|
committer | Robert Haas | 2020-01-27 16:04:51 +0000 |
commit | 1f3a021730be98b880d94cabbe21de7e4d8136f5 (patch) | |
tree | a1eacb3d158bef0a800bb751906bc2640e5eb908 | |
parent | 3e4818e9dd5be294d97ca67012528cb1c0b0ccaa (diff) |
Adjust pg_parse_json() so that it does not directly ereport().
Instead, it now returns a value indicating either success or the
type of error which occurred. The old behavior is still available
by calling pg_parse_json_or_ereport(). If the new interface is
used, an error can be thrown by passing the return value of
pg_parse_json() to json_ereport_error().
pg_parse_json() can still elog() in can't-happen cases, but it
seems like that issue is best handled separately.
Adjust json_lex() and json_count_array_elements() to return an
error code, too.
This is all in preparation for making the backend's json parser
available to frontend code.
Reviewed and/or tested by Mark Dilger and Andrew Dunstan.
Discussion: http://postgr.es/m/CA+TgmoYfOXhd27MUDGioVh6QtpD0C1K-f6ObSA10AWiHBAL5bA@mail.gmail.com
-rw-r--r-- | src/backend/utils/adt/json.c | 9 | ||||
-rw-r--r-- | src/backend/utils/adt/jsonapi.c | 537 | ||||
-rw-r--r-- | src/backend/utils/adt/jsonb.c | 4 | ||||
-rw-r--r-- | src/backend/utils/adt/jsonfuncs.c | 29 | ||||
-rw-r--r-- | src/include/utils/jsonapi.h | 46 |
5 files changed, 342 insertions, 283 deletions
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 4be16b5c20..e73a60ece8 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -81,7 +81,7 @@ json_in(PG_FUNCTION_ARGS) /* validate it */ lex = makeJsonLexContext(result, false); - pg_parse_json(lex, &nullSemAction); + pg_parse_json_or_ereport(lex, &nullSemAction); /* Internal representation is the same as text, for now */ PG_RETURN_TEXT_P(result); @@ -128,7 +128,7 @@ json_recv(PG_FUNCTION_ARGS) /* Validate it. */ lex = makeJsonLexContextCstringLen(str, nbytes, false); - pg_parse_json(lex, &nullSemAction); + pg_parse_json_or_ereport(lex, &nullSemAction); PG_RETURN_TEXT_P(cstring_to_text_with_len(str, nbytes)); } @@ -1337,12 +1337,15 @@ json_typeof(PG_FUNCTION_ARGS) JsonLexContext *lex; JsonTokenType tok; char *type; + JsonParseErrorType result; json = PG_GETARG_TEXT_PP(0); lex = makeJsonLexContext(json, false); /* Lex exactly one token from the input and check its type. */ - json_lex(lex); + result = json_lex(lex); + if (result != JSON_SUCCESS) + json_ereport_error(result, lex); tok = lex->token_type; switch (tok) { diff --git a/src/backend/utils/adt/jsonapi.c b/src/backend/utils/adt/jsonapi.c index 9e14306b6f..129fbd65d5 100644 --- a/src/backend/utils/adt/jsonapi.c +++ b/src/backend/utils/adt/jsonapi.c @@ -35,18 +35,17 @@ typedef enum /* contexts of JSON parser */ JSON_PARSE_END /* saw the end of a document, expect nothing */ } JsonParseContext; -static inline void json_lex_string(JsonLexContext *lex); -static inline void json_lex_number(JsonLexContext *lex, char *s, +static inline JsonParseErrorType json_lex_string(JsonLexContext *lex); +static inline JsonParseErrorType json_lex_number(JsonLexContext *lex, char *s, bool *num_err, int *total_len); -static inline void parse_scalar(JsonLexContext *lex, JsonSemAction *sem); -static void parse_object_field(JsonLexContext *lex, JsonSemAction *sem); -static void parse_object(JsonLexContext *lex, JsonSemAction *sem); -static void parse_array_element(JsonLexContext *lex, JsonSemAction *sem); -static void parse_array(JsonLexContext *lex, JsonSemAction *sem); -static void report_parse_error(JsonParseContext ctx, JsonLexContext *lex) pg_attribute_noreturn(); -static void report_invalid_token(JsonLexContext *lex) pg_attribute_noreturn(); +static inline JsonParseErrorType parse_scalar(JsonLexContext *lex, JsonSemAction *sem); +static JsonParseErrorType parse_object_field(JsonLexContext *lex, JsonSemAction *sem); +static JsonParseErrorType parse_object(JsonLexContext *lex, JsonSemAction *sem); +static JsonParseErrorType parse_array_element(JsonLexContext *lex, JsonSemAction *sem); +static JsonParseErrorType parse_array(JsonLexContext *lex, JsonSemAction *sem); +static JsonParseErrorType report_parse_error(JsonParseContext ctx, JsonLexContext *lex); static int report_json_context(JsonLexContext *lex); -static char *extract_mb_char(char *s); +static char *extract_token(JsonLexContext *lex); /* the null action object used for pure validation */ JsonSemAction nullSemAction = @@ -74,13 +73,13 @@ lex_peek(JsonLexContext *lex) * move the lexer to the next token if the current look_ahead token matches * the parameter token. Otherwise, report an error. */ -static inline void +static inline JsonParseErrorType lex_expect(JsonParseContext ctx, JsonLexContext *lex, JsonTokenType token) { if (lex_peek(lex) == token) - json_lex(lex); + return json_lex(lex); else - report_parse_error(ctx, lex); + return report_parse_error(ctx, lex); } /* chars to consider as part of an alphanumeric token */ @@ -171,13 +170,16 @@ makeJsonLexContextCstringLen(char *json, int len, bool need_escapes) * action routines to be called at appropriate spots during parsing, and a * pointer to a state object to be passed to those routines. */ -void +JsonParseErrorType pg_parse_json(JsonLexContext *lex, JsonSemAction *sem) { JsonTokenType tok; + JsonParseErrorType result; /* get the initial token */ - json_lex(lex); + result = json_lex(lex); + if (result != JSON_SUCCESS) + return result; tok = lex_peek(lex); @@ -185,17 +187,36 @@ pg_parse_json(JsonLexContext *lex, JsonSemAction *sem) switch (tok) { case JSON_TOKEN_OBJECT_START: - parse_object(lex, sem); + result = parse_object(lex, sem); break; case JSON_TOKEN_ARRAY_START: - parse_array(lex, sem); + result = parse_array(lex, sem); break; default: - parse_scalar(lex, sem); /* json can be a bare scalar */ + result = parse_scalar(lex, sem); /* json can be a bare scalar */ } - lex_expect(JSON_PARSE_END, lex, JSON_TOKEN_END); + if (result == JSON_SUCCESS) + result = lex_expect(JSON_PARSE_END, lex, JSON_TOKEN_END); + + return result; +} + +/* + * pg_parse_json_or_ereport + * + * This fuction is like pg_parse_json, except that it does not return a + * JsonParseErrorType. Instead, in case of any failure, this function will + * ereport(ERROR). + */ +void +pg_parse_json_or_ereport(JsonLexContext *lex, JsonSemAction *sem) +{ + JsonParseErrorType result; + result = pg_parse_json(lex, sem); + if (result != JSON_SUCCESS) + json_ereport_error(result, lex); } /* @@ -206,11 +227,12 @@ pg_parse_json(JsonLexContext *lex, JsonSemAction *sem) * * Designed to be called from array_start routines. */ -int -json_count_array_elements(JsonLexContext *lex) +JsonParseErrorType +json_count_array_elements(JsonLexContext *lex, int *elements) { JsonLexContext copylex; int count; + JsonParseErrorType result; /* * It's safe to do this with a shallow copy because the lexical routines @@ -222,21 +244,32 @@ json_count_array_elements(JsonLexContext *lex) copylex.lex_level++; count = 0; - lex_expect(JSON_PARSE_ARRAY_START, ©lex, JSON_TOKEN_ARRAY_START); + result = lex_expect(JSON_PARSE_ARRAY_START, ©lex, + JSON_TOKEN_ARRAY_START); + if (result != JSON_SUCCESS) + return result; if (lex_peek(©lex) != JSON_TOKEN_ARRAY_END) { while (1) { count++; - parse_array_element(©lex, &nullSemAction); + result = parse_array_element(©lex, &nullSemAction); + if (result != JSON_SUCCESS) + return result; if (copylex.token_type != JSON_TOKEN_COMMA) break; - json_lex(©lex); + result = json_lex(©lex); + if (result != JSON_SUCCESS) + return result; } } - lex_expect(JSON_PARSE_ARRAY_NEXT, ©lex, JSON_TOKEN_ARRAY_END); + result = lex_expect(JSON_PARSE_ARRAY_NEXT, ©lex, + JSON_TOKEN_ARRAY_END); + if (result != JSON_SUCCESS) + return result; - return count; + *elements = count; + return JSON_SUCCESS; } /* @@ -248,25 +281,23 @@ json_count_array_elements(JsonLexContext *lex) * - object ( { } ) * - object field */ -static inline void +static inline JsonParseErrorType parse_scalar(JsonLexContext *lex, JsonSemAction *sem) { char *val = NULL; json_scalar_action sfunc = sem->scalar; JsonTokenType tok = lex_peek(lex); + JsonParseErrorType result; /* a scalar must be a string, a number, true, false, or null */ if (tok != JSON_TOKEN_STRING && tok != JSON_TOKEN_NUMBER && tok != JSON_TOKEN_TRUE && tok != JSON_TOKEN_FALSE && tok != JSON_TOKEN_NULL) - report_parse_error(JSON_PARSE_VALUE, lex); + return report_parse_error(JSON_PARSE_VALUE, lex); /* if no semantic function, just consume the token */ if (sfunc == NULL) - { - json_lex(lex); - return; - } + return json_lex(lex); /* extract the de-escaped string value, or the raw lexeme */ if (lex_peek(lex) == JSON_TOKEN_STRING) @@ -284,13 +315,17 @@ parse_scalar(JsonLexContext *lex, JsonSemAction *sem) } /* consume the token */ - json_lex(lex); + result = json_lex(lex); + if (result != JSON_SUCCESS) + return result; /* invoke the callback */ (*sfunc) (sem->semstate, val, tok); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType parse_object_field(JsonLexContext *lex, JsonSemAction *sem) { /* @@ -304,14 +339,19 @@ parse_object_field(JsonLexContext *lex, JsonSemAction *sem) json_ofield_action oend = sem->object_field_end; bool isnull; JsonTokenType tok; + JsonParseErrorType result; if (lex_peek(lex) != JSON_TOKEN_STRING) - report_parse_error(JSON_PARSE_STRING, lex); + return report_parse_error(JSON_PARSE_STRING, lex); if ((ostart != NULL || oend != NULL) && lex->strval != NULL) fname = pstrdup(lex->strval->data); - json_lex(lex); + result = json_lex(lex); + if (result != JSON_SUCCESS) + return result; - lex_expect(JSON_PARSE_OBJECT_LABEL, lex, JSON_TOKEN_COLON); + result = lex_expect(JSON_PARSE_OBJECT_LABEL, lex, JSON_TOKEN_COLON); + if (result != JSON_SUCCESS) + return result; tok = lex_peek(lex); isnull = tok == JSON_TOKEN_NULL; @@ -322,20 +362,23 @@ parse_object_field(JsonLexContext *lex, JsonSemAction *sem) switch (tok) { case JSON_TOKEN_OBJECT_START: - parse_object(lex, sem); + result = parse_object(lex, sem); break; case JSON_TOKEN_ARRAY_START: - parse_array(lex, sem); + result = parse_array(lex, sem); break; default: - parse_scalar(lex, sem); + result = parse_scalar(lex, sem); } + if (result != JSON_SUCCESS) + return result; if (oend != NULL) (*oend) (sem->semstate, fname, isnull); + return JSON_SUCCESS; } -static void +static JsonParseErrorType parse_object(JsonLexContext *lex, JsonSemAction *sem) { /* @@ -345,6 +388,7 @@ parse_object(JsonLexContext *lex, JsonSemAction *sem) json_struct_action ostart = sem->object_start; json_struct_action oend = sem->object_end; JsonTokenType tok; + JsonParseErrorType result; check_stack_depth(); @@ -360,40 +404,51 @@ parse_object(JsonLexContext *lex, JsonSemAction *sem) lex->lex_level++; Assert(lex_peek(lex) == JSON_TOKEN_OBJECT_START); - json_lex(lex); + result = json_lex(lex); + if (result != JSON_SUCCESS) + return result; tok = lex_peek(lex); switch (tok) { case JSON_TOKEN_STRING: - parse_object_field(lex, sem); - while (lex_peek(lex) == JSON_TOKEN_COMMA) + result = parse_object_field(lex, sem); + while (result == JSON_SUCCESS && lex_peek(lex) == JSON_TOKEN_COMMA) { - json_lex(lex); - parse_object_field(lex, sem); + result = json_lex(lex); + if (result != JSON_SUCCESS) + break; + result = parse_object_field(lex, sem); } break; case JSON_TOKEN_OBJECT_END: break; default: /* case of an invalid initial token inside the object */ - report_parse_error(JSON_PARSE_OBJECT_START, lex); + result = report_parse_error(JSON_PARSE_OBJECT_START, lex); } + if (result != JSON_SUCCESS) + return result; - lex_expect(JSON_PARSE_OBJECT_NEXT, lex, JSON_TOKEN_OBJECT_END); + result = lex_expect(JSON_PARSE_OBJECT_NEXT, lex, JSON_TOKEN_OBJECT_END); + if (result != JSON_SUCCESS) + return result; lex->lex_level--; if (oend != NULL) (*oend) (sem->semstate); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType parse_array_element(JsonLexContext *lex, JsonSemAction *sem) { json_aelem_action astart = sem->array_element_start; json_aelem_action aend = sem->array_element_end; JsonTokenType tok = lex_peek(lex); + JsonParseErrorType result; bool isnull; @@ -406,20 +461,25 @@ parse_array_element(JsonLexContext *lex, JsonSemAction *sem) switch (tok) { case JSON_TOKEN_OBJECT_START: - parse_object(lex, sem); + result = parse_object(lex, sem); break; case JSON_TOKEN_ARRAY_START: - parse_array(lex, sem); + result = parse_array(lex, sem); break; default: - parse_scalar(lex, sem); + result = parse_scalar(lex, sem); } + if (result != JSON_SUCCESS) + return result; + if (aend != NULL) (*aend) (sem->semstate, isnull); + + return JSON_SUCCESS; } -static void +static JsonParseErrorType parse_array(JsonLexContext *lex, JsonSemAction *sem) { /* @@ -428,6 +488,7 @@ parse_array(JsonLexContext *lex, JsonSemAction *sem) */ json_struct_action astart = sem->array_start; json_struct_action aend = sem->array_end; + JsonParseErrorType result; check_stack_depth(); @@ -442,35 +503,43 @@ parse_array(JsonLexContext *lex, JsonSemAction *sem) */ lex->lex_level++; - lex_expect(JSON_PARSE_ARRAY_START, lex, JSON_TOKEN_ARRAY_START); - if (lex_peek(lex) != JSON_TOKEN_ARRAY_END) + result = lex_expect(JSON_PARSE_ARRAY_START, lex, JSON_TOKEN_ARRAY_START); + if (result == JSON_SUCCESS && lex_peek(lex) != JSON_TOKEN_ARRAY_END) { + result = parse_array_element(lex, sem); - parse_array_element(lex, sem); - - while (lex_peek(lex) == JSON_TOKEN_COMMA) + while (result == JSON_SUCCESS && lex_peek(lex) == JSON_TOKEN_COMMA) { - json_lex(lex); |