diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index e73a60ece8a..f6cd2b99115 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -23,7 +23,7 @@ #include "utils/date.h" #include "utils/datetime.h" #include "utils/json.h" -#include "utils/jsonapi.h" +#include "utils/jsonfuncs.h" #include "utils/lsyscache.h" #include "utils/typcache.h" diff --git a/src/backend/utils/adt/jsonapi.c b/src/backend/utils/adt/jsonapi.c index 129fbd65d51..1ac3b7beda8 100644 --- a/src/backend/utils/adt/jsonapi.c +++ b/src/backend/utils/adt/jsonapi.c @@ -44,7 +44,6 @@ 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_token(JsonLexContext *lex); /* the null action object used for pure validation */ @@ -128,25 +127,13 @@ IsValidJsonNumber(const char *str, int len) } /* - * makeJsonLexContext + * makeJsonLexContextCstringLen * - * lex constructor, with or without StringInfo object - * for de-escaped lexemes. + * lex constructor, with or without StringInfo object for de-escaped lexemes. * * Without is better as it makes the processing faster, so only make one * if really required. - * - * If you already have the json as a text* value, use the first of these - * functions, otherwise use makeJsonLexContextCstringLen(). */ -JsonLexContext * -makeJsonLexContext(text *json, bool need_escapes) -{ - return makeJsonLexContextCstringLen(VARDATA_ANY(json), - VARSIZE_ANY_EXHDR(json), - need_escapes); -} - JsonLexContext * makeJsonLexContextCstringLen(char *json, int len, bool need_escapes) { @@ -202,23 +189,6 @@ pg_parse_json(JsonLexContext *lex, JsonSemAction *sem) 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); -} - /* * json_count_array_elements * @@ -1038,27 +1008,6 @@ report_parse_error(JsonParseContext ctx, JsonLexContext *lex) } } -/* - * Report a JSON error. - */ -void -json_ereport_error(JsonParseErrorType error, JsonLexContext *lex) -{ - if (error == JSON_UNICODE_HIGH_ESCAPE || - error == JSON_UNICODE_CODE_POINT_ZERO) - ereport(ERROR, - (errcode(ERRCODE_UNTRANSLATABLE_CHARACTER), - errmsg("unsupported Unicode escape sequence"), - errdetail("%s", json_errdetail(error, lex)), - report_json_context(lex))); - else - ereport(ERROR, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("invalid input syntax for type %s", "json"), - errdetail("%s", json_errdetail(error, lex)), - report_json_context(lex))); -} - /* * Construct a detail message for a JSON error. */ @@ -1118,78 +1067,6 @@ json_errdetail(JsonParseErrorType error, JsonLexContext *lex) } } -/* - * Report a CONTEXT line for bogus JSON input. - * - * lex->token_terminator must be set to identify the spot where we detected - * the error. Note that lex->token_start might be NULL, in case we recognized - * error at EOF. - * - * The return value isn't meaningful, but we make it non-void so that this - * can be invoked inside ereport(). - */ -static int -report_json_context(JsonLexContext *lex) -{ - const char *context_start; - const char *context_end; - const char *line_start; - int line_number; - char *ctxt; - int ctxtlen; - const char *prefix; - const char *suffix; - - /* Choose boundaries for the part of the input we will display */ - context_start = lex->input; - context_end = lex->token_terminator; - line_start = context_start; - line_number = 1; - for (;;) - { - /* Always advance over newlines */ - if (context_start < context_end && *context_start == '\n') - { - context_start++; - line_start = context_start; - line_number++; - continue; - } - /* Otherwise, done as soon as we are close enough to context_end */ - if (context_end - context_start < 50) - break; - /* Advance to next multibyte character */ - if (IS_HIGHBIT_SET(*context_start)) - context_start += pg_mblen(context_start); - else - context_start++; - } - - /* - * We add "..." to indicate that the excerpt doesn't start at the - * beginning of the line ... but if we're within 3 characters of the - * beginning of the line, we might as well just show the whole line. - */ - if (context_start - line_start <= 3) - context_start = line_start; - - /* Get a null-terminated copy of the data to present */ - ctxtlen = context_end - context_start; - ctxt = palloc(ctxtlen + 1); - memcpy(ctxt, context_start, ctxtlen); - ctxt[ctxtlen] = '\0'; - - /* - * Show the context, prefixing "..." if not starting at start of line, and - * suffixing "..." if not ending at end of line. - */ - prefix = (context_start > line_start) ? "..." : ""; - suffix = (lex->token_type != JSON_TOKEN_END && context_end - lex->input < lex->input_length && *context_end != '\n' && *context_end != '\r') ? "..." : ""; - - return errcontext("JSON data, line %d: %s%s%s", - line_number, prefix, ctxt, suffix); -} - /* * Extract the current token from a lexing context, for error reporting. */ diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index 83d7f68b821..c912f8932df 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -23,8 +23,8 @@ #include "utils/date.h" #include "utils/datetime.h" #include "utils/json.h" -#include "utils/jsonapi.h" #include "utils/jsonb.h" +#include "utils/jsonfuncs.h" #include "utils/lsyscache.h" #include "utils/syscache.h" #include "utils/typcache.h" diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index 9eff5068553..66ea11b971c 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -329,6 +329,8 @@ typedef struct JsObject hash_destroy((jso)->val.json_hash); \ } while (0) +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); @@ -484,6 +486,37 @@ static void transform_string_values_object_field_start(void *state, char *fname, static void transform_string_values_array_element_start(void *state, bool isnull); static void transform_string_values_scalar(void *state, char *token, JsonTokenType tokentype); +/* + * 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); +} + +/* + * makeJsonLexContext + * + * This is like makeJsonLexContextCstringLen, but it accepts a text value + * directly. + */ +JsonLexContext * +makeJsonLexContext(text *json, bool need_escapes) +{ + return makeJsonLexContextCstringLen(VARDATA_ANY(json), + VARSIZE_ANY_EXHDR(json), + need_escapes); +} + /* * SQL function json_object_keys * @@ -573,6 +606,99 @@ jsonb_object_keys(PG_FUNCTION_ARGS) SRF_RETURN_DONE(funcctx); } +/* + * Report a JSON error. + */ +void +json_ereport_error(JsonParseErrorType error, JsonLexContext *lex) +{ + if (error == JSON_UNICODE_HIGH_ESCAPE || + error == JSON_UNICODE_CODE_POINT_ZERO) + ereport(ERROR, + (errcode(ERRCODE_UNTRANSLATABLE_CHARACTER), + errmsg("unsupported Unicode escape sequence"), + errdetail("%s", json_errdetail(error, lex)), + report_json_context(lex))); + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s", "json"), + errdetail("%s", json_errdetail(error, lex)), + report_json_context(lex))); +} + +/* + * Report a CONTEXT line for bogus JSON input. + * + * lex->token_terminator must be set to identify the spot where we detected + * the error. Note that lex->token_start might be NULL, in case we recognized + * error at EOF. + * + * The return value isn't meaningful, but we make it non-void so that this + * can be invoked inside ereport(). + */ +static int +report_json_context(JsonLexContext *lex) +{ + const char *context_start; + const char *context_end; + const char *line_start; + int line_number; + char *ctxt; + int ctxtlen; + const char *prefix; + const char *suffix; + + /* Choose boundaries for the part of the input we will display */ + context_start = lex->input; + context_end = lex->token_terminator; + line_start = context_start; + line_number = 1; + for (;;) + { + /* Always advance over newlines */ + if (context_start < context_end && *context_start == '\n') + { + context_start++; + line_start = context_start; + line_number++; + continue; + } + /* Otherwise, done as soon as we are close enough to context_end */ + if (context_end - context_start < 50) + break; + /* Advance to next multibyte character */ + if (IS_HIGHBIT_SET(*context_start)) + context_start += pg_mblen(context_start); + else + context_start++; + } + + /* + * We add "..." to indicate that the excerpt doesn't start at the + * beginning of the line ... but if we're within 3 characters of the + * beginning of the line, we might as well just show the whole line. + */ + if (context_start - line_start <= 3) + context_start = line_start; + + /* Get a null-terminated copy of the data to present */ + ctxtlen = context_end - context_start; + ctxt = palloc(ctxtlen + 1); + memcpy(ctxt, context_start, ctxtlen); + ctxt[ctxtlen] = '\0'; + + /* + * Show the context, prefixing "..." if not starting at start of line, and + * suffixing "..." if not ending at end of line. + */ + prefix = (context_start > line_start) ? "..." : ""; + suffix = (lex->token_type != JSON_TOKEN_END && context_end - lex->input < lex->input_length && *context_end != '\n' && *context_end != '\r') ? "..." : ""; + + return errcontext("JSON data, line %d: %s%s%s", + line_number, prefix, ctxt, suffix); +} + Datum json_object_keys(PG_FUNCTION_ARGS) diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h index 74dc35c41c6..4d69b184951 100644 --- a/src/include/utils/jsonapi.h +++ b/src/include/utils/jsonapi.h @@ -126,12 +126,6 @@ typedef struct JsonSemAction extern JsonParseErrorType pg_parse_json(JsonLexContext *lex, JsonSemAction *sem); -/* - * Same thing, but signal errors via ereport(ERROR) instead of returning - * a result code. - */ -extern void pg_parse_json_or_ereport(JsonLexContext *lex, JsonSemAction *sem); - /* the null action object used for pure validation */ extern JsonSemAction nullSemAction; @@ -148,15 +142,11 @@ extern JsonParseErrorType json_count_array_elements(JsonLexContext *lex, int *elements); /* - * constructors for JsonLexContext, with or without strval element. + * constructor for JsonLexContext, with or without strval element. * If supplied, the strval element will contain a de-escaped version of * the lexeme. However, doing this imposes a performance penalty, so * it should be avoided if the de-escaped lexeme is not required. - * - * If you already have the json as a text* value, use the first of these - * functions, otherwise use makeJsonLexContextCstringLen(). */ -extern JsonLexContext *makeJsonLexContext(text *json, bool need_escapes); extern JsonLexContext *makeJsonLexContextCstringLen(char *json, int len, bool need_escapes); @@ -164,9 +154,6 @@ extern JsonLexContext *makeJsonLexContextCstringLen(char *json, /* lex one token */ extern JsonParseErrorType json_lex(JsonLexContext *lex); -/* report an error during json lexing or parsing */ -extern void json_ereport_error(JsonParseErrorType error, JsonLexContext *lex); - /* construct an error detail string for a json error */ extern char *json_errdetail(JsonParseErrorType error, JsonLexContext *lex); diff --git a/src/include/utils/jsonfuncs.h b/src/include/utils/jsonfuncs.h index 19f087ccae9..b993f38409f 100644 --- a/src/include/utils/jsonfuncs.h +++ b/src/include/utils/jsonfuncs.h @@ -36,6 +36,15 @@ typedef void (*JsonIterateStringValuesAction) (void *state, char *elem_value, in /* an action that will be applied to each value in transform_json(b)_values functions */ typedef text *(*JsonTransformStringValuesAction) (void *state, char *elem_value, int elem_len); +/* 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); + +/* report an error during json lexing or parsing */ +extern void json_ereport_error(JsonParseErrorType error, JsonLexContext *lex); + extern uint32 parse_jsonb_index_flags(Jsonb *jb); extern void iterate_jsonb_values(Jsonb *jb, uint32 flags, void *state, JsonIterateStringValuesAction action);