diff options
Diffstat (limited to 'src/backend/utils/adt/jsonfuncs.c')
-rw-r--r-- | src/backend/utils/adt/jsonfuncs.c | 575 |
1 files changed, 296 insertions, 279 deletions
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index 5fabef0de96..2d00dbed4bd 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -40,21 +40,20 @@ static void 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); /* common worker function for json getter functions */ -static Datum get_path_all(FunctionCallInfo fcinfo, const char *funcname, - bool as_text); -static text *get_worker(text *json, char *field, int elem_index, - char **tpath, int *ipath, int npath, +static Datum get_path_all(FunctionCallInfo fcinfo, bool as_text); +static text *get_worker(text *json, char **tpath, int *ipath, int npath, bool normalize_results); -static Datum get_jsonb_path_all(FunctionCallInfo fcinfo, const char *funcname, - bool as_text); +static Datum get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text); /* semantic action functions for json_array_length */ static void alen_object_start(void *state); @@ -116,14 +115,6 @@ static JsonbValue *findJsonbValueFromContainerLen(JsonbContainer *container, char *key, uint32 keylen); -/* search type classification for json_get* functions */ -typedef enum -{ - JSON_SEARCH_OBJECT = 1, - JSON_SEARCH_ARRAY, - JSON_SEARCH_PATH -} JsonSearch; - /* state for json_object_keys */ typedef struct OkeysState { @@ -138,21 +129,15 @@ typedef struct OkeysState typedef struct GetState { JsonLexContext *lex; - JsonSearch search_type; - int search_index; - int array_index; - char *search_term; - char *result_start; text *tresult; - bool result_is_null; + char *result_start; bool normalize_results; bool next_scalar; - char **path; - int npath; - char **current_path; - bool *pathok; - int *array_level_index; - int *path_level_index; + int npath; /* length of each path-related array */ + char **path_names; /* field name(s) being sought */ + int *path_indexes; /* array index(es) being sought */ + bool *pathok; /* is path matched to current depth? */ + int *array_cur_index; /* current element index at each path level */ } GetState; /* state for json_array_length */ @@ -455,11 +440,11 @@ Datum json_object_field(PG_FUNCTION_ARGS) { text *json = PG_GETARG_TEXT_P(0); - text *fname = PG_GETARG_TEXT_P(1); + text *fname = PG_GETARG_TEXT_PP(1); char *fnamestr = text_to_cstring(fname); text *result; - result = get_worker(json, fnamestr, -1, NULL, NULL, -1, false); + result = get_worker(json, &fnamestr, NULL, 1, false); if (result != NULL) PG_RETURN_TEXT_P(result); @@ -474,21 +459,12 @@ jsonb_object_field(PG_FUNCTION_ARGS) text *key = PG_GETARG_TEXT_PP(1); JsonbValue *v; - if (JB_ROOT_IS_SCALAR(jb)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot call %s on a scalar", - "jsonb_object_field (jsonb -> text)"))); - else if (JB_ROOT_IS_ARRAY(jb)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot call %s on an array", - "jsonb_object_field (jsonb -> text)"))); - - Assert(JB_ROOT_IS_OBJECT(jb)); + if (!JB_ROOT_IS_OBJECT(jb)) + PG_RETURN_NULL(); v = findJsonbValueFromContainerLen(&jb->root, JB_FOBJECT, - VARDATA_ANY(key), VARSIZE_ANY_EXHDR(key)); + VARDATA_ANY(key), + VARSIZE_ANY_EXHDR(key)); if (v != NULL) PG_RETURN_JSONB(JsonbValueToJsonb(v)); @@ -500,11 +476,11 @@ Datum json_object_field_text(PG_FUNCTION_ARGS) { text *json = PG_GETARG_TEXT_P(0); - text *fname = PG_GETARG_TEXT_P(1); + text *fname = PG_GETARG_TEXT_PP(1); char *fnamestr = text_to_cstring(fname); text *result; - result = get_worker(json, fnamestr, -1, NULL, NULL, -1, true); + result = get_worker(json, &fnamestr, NULL, 1, true); if (result != NULL) PG_RETURN_TEXT_P(result); @@ -519,21 +495,12 @@ jsonb_object_field_text(PG_FUNCTION_ARGS) text *key = PG_GETARG_TEXT_PP(1); JsonbValue *v; - if (JB_ROOT_IS_SCALAR(jb)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot call %s on a scalar", - "jsonb_object_field_text (jsonb ->> text)"))); - else if (JB_ROOT_IS_ARRAY(jb)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot call %s on an array", - "jsonb_object_field_text (jsonb ->> text)"))); - - Assert(JB_ROOT_IS_OBJECT(jb)); + if (!JB_ROOT_IS_OBJECT(jb)) + PG_RETURN_NULL(); v = findJsonbValueFromContainerLen(&jb->root, JB_FOBJECT, - VARDATA_ANY(key), VARSIZE_ANY_EXHDR(key)); + VARDATA_ANY(key), + VARSIZE_ANY_EXHDR(key)); if (v != NULL) { @@ -579,7 +546,7 @@ json_array_element(PG_FUNCTION_ARGS) int element = PG_GETARG_INT32(1); text *result; - result = get_worker(json, NULL, element, NULL, NULL, -1, false); + result = get_worker(json, NULL, &element, 1, false); if (result != NULL) PG_RETURN_TEXT_P(result); @@ -594,18 +561,8 @@ jsonb_array_element(PG_FUNCTION_ARGS) int element = PG_GETARG_INT32(1); JsonbValue *v; - if (JB_ROOT_IS_SCALAR(jb)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot call %s on a scalar", - "jsonb_array_element (jsonb -> int)"))); - else if (JB_ROOT_IS_OBJECT(jb)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot call %s on an object", - "jsonb_array_element (jsonb -> int)"))); - - Assert(JB_ROOT_IS_ARRAY(jb)); + if (!JB_ROOT_IS_ARRAY(jb)) + PG_RETURN_NULL(); v = getIthJsonbValueFromContainer(&jb->root, element); if (v != NULL) @@ -621,7 +578,7 @@ json_array_element_text(PG_FUNCTION_ARGS) int element = PG_GETARG_INT32(1); text *result; - result = get_worker(json, NULL, element, NULL, NULL, -1, true); + result = get_worker(json, NULL, &element, 1, true); if (result != NULL) PG_RETURN_TEXT_P(result); @@ -636,18 +593,8 @@ jsonb_array_element_text(PG_FUNCTION_ARGS) int element = PG_GETARG_INT32(1); JsonbValue *v; - if (JB_ROOT_IS_SCALAR(jb)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot call %s on a scalar", - "jsonb_array_element_text"))); - else if (JB_ROOT_IS_OBJECT(jb)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot call %s on an object", - "jsonb_array_element_text"))); - - Assert(JB_ROOT_IS_ARRAY(jb)); + if (!JB_ROOT_IS_ARRAY(jb)) + PG_RETURN_NULL(); v = getIthJsonbValueFromContainer(&jb->root, element); if (v != NULL) @@ -690,20 +637,20 @@ jsonb_array_element_text(PG_FUNCTION_ARGS) Datum json_extract_path(PG_FUNCTION_ARGS) { - return get_path_all(fcinfo, "json_extract_path", false); + return get_path_all(fcinfo, false); } Datum json_extract_path_text(PG_FUNCTION_ARGS) { - return get_path_all(fcinfo, "json_extract_path_text", true); + return get_path_all(fcinfo, true); } /* * common routine for extract_path functions */ static Datum -get_path_all(FunctionCallInfo fcinfo, const char *funcname, bool as_text) +get_path_all(FunctionCallInfo fcinfo, bool as_text) { text *json = PG_GETARG_TEXT_P(0); ArrayType *path = PG_GETARG_ARRAYTYPE_P(1); @@ -714,55 +661,54 @@ get_path_all(FunctionCallInfo fcinfo, const char *funcname, bool as_text) char **tpath; int *ipath; int i; - long ind; - char *endptr; + /* + * If the array contains any null elements, return NULL, on the grounds + * that you'd have gotten NULL if any RHS value were NULL in a nested + * series of applications of the -> operator. (Note: because we also + * return NULL for error cases such as no-such-field, this is true + * regardless of the contents of the rest of the array.) + */ if (array_contains_nulls(path)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot call %s with null path elements", - funcname))); + PG_RETURN_NULL(); deconstruct_array(path, TEXTOID, -1, false, 'i', &pathtext, &pathnulls, &npath); - /* - * If the array is empty, return NULL; this is dubious but it's what 9.3 - * did. - */ - if (npath <= 0) - PG_RETURN_NULL(); - tpath = palloc(npath * sizeof(char *)); ipath = palloc(npath * sizeof(int)); for (i = 0; i < npath; i++) { + Assert(!pathnulls[i]); tpath[i] = TextDatumGetCString(pathtext[i]); - if (*tpath[i] == '\0') - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot call %s with empty path elements", - funcname))); /* * we have no idea at this stage what structure the document is so * just convert anything in the path that we can to an integer and set * all the other integers to -1 which will never match. */ - ind = strtol(tpath[i], &endptr, 10); - if (*endptr == '\0' && ind <= INT_MAX && ind >= 0) - ipath[i] = (int) ind; + if (*tpath[i] != '\0') + { + long ind; + char *endptr; + + errno = 0; + ind = strtol(tpath[i], &endptr, 10); + if (*endptr == '\0' && errno == 0 && ind <= INT_MAX && ind >= 0) + ipath[i] = (int) ind; + else + ipath[i] = -1; + } else ipath[i] = -1; } - result = get_worker(json, NULL, -1, tpath, ipath, npath, as_text); + result = get_worker(json, tpath, ipath, npath, as_text); if (result != NULL) PG_RETURN_TEXT_P(result); else - /* null is NULL, regardless */ PG_RETURN_NULL(); } @@ -770,55 +716,42 @@ get_path_all(FunctionCallInfo fcinfo, const char *funcname, bool as_text) * get_worker * * common worker for all the json getter functions + * + * json: JSON object (in text form) + * tpath[]: field name(s) to extract + * ipath[]: array index(es) (zero-based) to extract + * npath: length of tpath[] and/or ipath[] + * normalize_results: true to de-escape string and null scalars + * + * tpath can be NULL, or any one tpath[] entry can be NULL, if an object + * field is not to be matched at that nesting level. Similarly, ipath can + * be NULL, or any one ipath[] entry can be -1, if an array element is not + * to be matched at that nesting level. */ static text * get_worker(text *json, - char *field, - int elem_index, char **tpath, int *ipath, int npath, bool normalize_results) { - GetState *state; JsonLexContext *lex = makeJsonLexContext(json, true); - JsonSemAction *sem; + JsonSemAction *sem = palloc0(sizeof(JsonSemAction)); + GetState *state = palloc0(sizeof(GetState)); - /* only allowed to use one of these */ - Assert(elem_index < 0 || (tpath == NULL && ipath == NULL && field == NULL)); - Assert(tpath == NULL || field == NULL); - - state = palloc0(sizeof(GetState)); - sem = palloc0(sizeof(JsonSemAction)); + Assert(npath >= 0); state->lex = lex; /* is it "_as_text" variant? */ state->normalize_results = normalize_results; - if (field != NULL) - { - /* single text argument */ - state->search_type = JSON_SEARCH_OBJECT; - state->search_term = field; - } - else if (tpath != NULL) - { - /* path array argument */ - state->search_type = JSON_SEARCH_PATH; - state->path = tpath; - state->npath = npath; - state->current_path = palloc(sizeof(char *) * npath); - state->pathok = palloc0(sizeof(bool) * npath); + state->npath = npath; + state->path_names = tpath; + state->path_indexes = ipath; + state->pathok = palloc0(sizeof(bool) * npath); + state->array_cur_index = palloc(sizeof(int) * npath); + + if (npath > 0) state->pathok[0] = true; - state->array_level_index = palloc(sizeof(int) * npath); - state->path_level_index = ipath; - } - else - { - /* single integer argument */ - state->search_type = JSON_SEARCH_ARRAY; - state->search_index = elem_index; - state->array_index = -1; - } sem->semstate = (void *) state; @@ -826,16 +759,22 @@ get_worker(text *json, * Not all variants need all the semantic routines. Only set the ones that * are actually needed for maximum efficiency. */ - sem->object_start = get_object_start; - sem->array_start = get_array_start; sem->scalar = get_scalar; - if (field != NULL || tpath != NULL) + if (npath == 0) + { + sem->object_start = get_object_start; + sem->object_end = get_object_end; + sem->array_start = get_array_start; + sem->array_end = get_array_end; + } + if (tpath != NULL) { sem->object_field_start = get_object_field_start; sem->object_field_end = get_object_field_end; } - if (field == NULL) + if (ipath != NULL) { + sem->array_start = get_array_start; sem->array_element_start = get_array_element_start; sem->array_element_end = get_array_element_end; } @@ -849,50 +788,66 @@ static void get_object_start(void *state) { GetState *_state = (GetState *) state; + int lex_level = _state->lex->lex_level; - /* json structure check */ - if (_state->lex->lex_level == 0 && _state->search_type == JSON_SEARCH_ARRAY) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot extract array element from a non-array"))); + if (lex_level == 0 && _state->npath == 0) + { + /* + * Special case: we should match the entire object. We only need this + * at outermost level because at nested levels the match will have + * been started by the outer field or array element callback. + */ + _state->result_start = _state->lex->token_start; + } } static void -get_object_field_start(void *state, char *fname, bool isnull) +get_object_end(void *state) { GetState *_state = (GetState *) state; - bool get_next = false; int lex_level = _state->lex->lex_level; - if (lex_level == 1 && _state->search_type == JSON_SEARCH_OBJECT && - strcmp(fname, _state->search_term) == 0) + if (lex_level == 0 && _state->npath == 0) { - _state->tresult = NULL; - _state->result_start = NULL; - get_next = true; + /* Special case: return the entire object */ + char *start = _state->result_start; + int len = _state->lex->prev_token_terminator - start; + + _state->tresult = cstring_to_text_with_len(start, len); } - else if (_state->search_type == JSON_SEARCH_PATH && - lex_level <= _state->npath && - _state->pathok[_state->lex->lex_level - 1] && - strcmp(fname, _state->path[lex_level - 1]) == 0) - { - /* path search, path so far is ok, and we have a match */ +} - /* this object overrides any previous matching object */ - _state->tresult = NULL; - _state->result_start = NULL; +static void +get_object_field_start(void *state, char *fname, bool isnull) +{ + GetState *_state = (GetState *) state; + bool get_next = false; + int lex_level = _state->lex->lex_level; - /* if not at end of path just mark path ok */ + if (lex_level <= _state->npath && + _state->pathok[lex_level - 1] && + _state->path_names != NULL && + _state->path_names[lex_level - 1] != NULL && + strcmp(fname, _state->path_names[lex_level - 1]) == 0) + { if (lex_level < _state->npath) + { + /* if not at end of path just mark path ok */ _state->pathok[lex_level] = true; - - /* end of path, so we want this value */ - if (lex_level == _state->npath) + } + else + { + /* end of path, so we want this value */ get_next = true; + } } if (get_next) { + /* this object overrides any previous matching object */ + _state->tresult = NULL; + _state->result_start = NULL; + if (_state->normalize_results && _state->lex->token_type == JSON_TOKEN_STRING) { @@ -914,26 +869,26 @@ get_object_field_end(void *state, char *fname, bool isnull) bool get_last = false; int lex_level = _state->lex->lex_level; - /* same tests as in get_object_field_start, mutatis mutandis */ - if (lex_level == 1 && _state->search_type == JSON_SEARCH_OBJECT && - strcmp(fname, _state->search_term) == 0) - { - get_last = true; - } - else if (_state->search_type == JSON_SEARCH_PATH && - lex_level <= _state->npath && - _state->pathok[lex_level - 1] && - strcmp(fname, _state->path[lex_level - 1]) == 0) + /* same tests as in get_object_field_start */ + if (lex_level <= _state->npath && + _state->pathok[lex_level - 1] && + _state->path_names != NULL && + _state->path_names[lex_level - 1] != NULL && + strcmp(fname, _state->path_names[lex_level - 1]) == 0) { - /* done with this field so reset pathok */ if (lex_level < _state->npath) + { + /* done with this field so reset pathok */ _state->pathok[lex_level] = false; - - if (lex_level == _state->npath) + } + else + { + /* end of path, so we want this value */ get_last = true; + } } - /* for as_test variants our work is already done */ + /* for as_text scalar case, our work is already done */ if (get_last && _state->result_start != NULL) { /* @@ -941,19 +896,19 @@ get_object_field_end(void *state, char *fname, bool isnull) * start up to the end of the previous token (the lexer is by now * ahead of us on whatever came after what we're interested in). */ - int len = _state->lex->prev_token_terminator - _state->result_start; - if (isnull && _state->normalize_results) _state->tresult = (text *) NULL; else - _state->tresult = cstring_to_text_with_len(_state->result_start, len); - } + { + char *start = _state->result_start; + int len = _state->lex->prev_token_terminator - start; - /* - * don't need to reset _state->result_start b/c we're only returning one - * datum, the conditions should not occur more than once, and this lets us - * check cheaply that they don't (see object_field_start() ) - */ + _state->tresult = cstring_to_text_with_len(start, len); + } + + /* this should be unnecessary but let's do it for cleanliness: */ + _state->result_start = NULL; + } } static void @@ -962,19 +917,36 @@ get_array_start(void *state) GetState *_state = (GetState *) state; int lex_level = _state->lex->lex_level; - /* json structure check */ - if (lex_level == 0 && _state->search_type == JSON_SEARCH_OBJECT) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot extract field from a non-object"))); + if (lex_level < _state->npath) + { + /* Initialize counting of elements in this array */ + _state->array_cur_index[lex_level] = -1; + } + else if (lex_level == 0 && _state->npath == 0) + { + /* + * Special case: we should match the entire array. We only need this + * at outermost level because at nested levels the match will have + * been started by the outer field or array element callback. + */ + _state->result_start = _state->lex->token_start; + } +} - /* - * initialize array count for this nesting level. Note: the lex_level seen - * by array_start is one less than that seen by the elements of the array. - */ - if (_state->search_type == JSON_SEARCH_PATH && - lex_level < _state->npath) - _state->array_level_index[lex_level] = -1; +static void +get_array_end(void *state) +{ + GetState *_state = (GetState *) state; + int lex_level = _state->lex->lex_level; + + if (lex_level == 0 && _state->npath == 0) + { + /* Special case: return the entire array */ + char *start = _state->result_start; + int len = _state->lex->prev_token_terminator - start; + + _state->tresult = cstring_to_text_with_len(start, len); + } } static void @@ -984,44 +956,33 @@ get_array_element_start(void *state, bool isnull) bool get_next = false; int lex_level = _state->lex->lex_level; - if (lex_level == 1 && _state->search_type == JSON_SEARCH_ARRAY) - { - /* single integer search */ - _state->array_index++; - if (_state->array_index == _state->search_index) - get_next = true; - } - else if (_state->search_type == JSON_SEARCH_PATH && - lex_level <= _state->npath && - _state->pathok[lex_level - 1]) + /* Update array element counter */ + if (lex_level <= _state->npath) + _state->array_cur_index[lex_level - 1]++; + + if (lex_level <= _state->npath && + _state->pathok[lex_level - 1] && + _state->path_indexes != NULL && + _state->array_cur_index[lex_level - 1] == _state->path_indexes[lex_level - 1]) { - /* - * path search, path so far is ok - * - * increment the array counter. no point doing this if we already know - * the path is bad. - * - * then check if we have a match. - */ - if (++_state->array_level_index[lex_level - 1] == - _state->path_level_index[lex_level - 1]) + if (lex_level < _state->npath) { - if (lex_level == _state->npath) - { - /* match and at end of path, so get value */ - get_next = true; - } - else - { - /* not at end of path just mark path ok */ - _state->pathok[lex_level] = true; - } + /* if not at end of path just mark path ok */ + _state->pathok[lex_level] = true; + } + else + { + /* end of path, so we want this value */ + get_next = true; } } /* same logic as for objects */ if (get_next) { + _state->tresult = NULL; + _state->result_start = NULL; + if (_state->normalize_results && _state->lex->token_type == JSON_TOKEN_STRING) { @@ -1041,34 +1002,38 @@ get_array_element_end(void *state, bool isnull) bool get_last = false; int lex_level = _state->lex->lex_level; - /* same logic as in get_object_end, modified for arrays */ - - if (lex_level == 1 && _state->search_type == JSON_SEARCH_ARRAY && - _state->array_index == _state->search_index) + /* same tests as in get_array_element_start */ + if (lex_level <= _state->npath && + _state->pathok[lex_level - 1] && + _state->path_indexes != NULL && + _state->array_cur_index[lex_level - 1] == _state->path_indexes[lex_level - 1]) { - get_last = true; - } - else if (_state->search_type == JSON_SEARCH_PATH && - lex_level <= _state->npath && - _state->pathok[lex_level - 1] && - _state->array_level_index[lex_level - 1] == - _state->path_level_index[lex_level - 1]) - { - /* done with this element so reset pathok */ if (lex_level < _state->npath) + { + /* done with this element so reset pathok */ _state->pathok[lex_level] = false; - - if (lex_level == _state->npath) + } + else + { + /* end of path, so we want this value */ get_last = true; + } } + + /* same logic as for objects */ if (get_last && _state->result_start != NULL) { - int len = _state->lex->prev_token_terminator - _state->result_start; - if (isnull && _state->normalize_results) _state->tresult = (text *) NULL; else - _state->tresult = cstring_to_text_with_len(_state->result_start, len); + { + char *start = _state->result_start; + int len = _state->lex->prev_token_terminator - start; + + _state->tresult = cstring_to_text_with_len(start, len); + } + + _state->result_start = NULL; } } @@ -1076,11 +1041,34 @@ static void get_scalar(void *state, char *token, JsonTokenType tokentype) { GetState *_state = (GetState *) state; + int lex_level = _state->lex->lex_level; + + /* Check for whole-object match */ + if (lex_level == 0 && _state->npath == 0) + { + if (_state->normalize_results && tokentype == JSON_TOKEN_STRING) + { + /* we want the de-escaped string */ + _state->next_scalar = true; + } + else if (_state->normalize_results && tokentype == JSON_TOKEN_NULL) + { + _state->tresult = (text *) NULL; + } + else + { + /* + * This is a bit hokey: we will suppress whitespace after the + * scalar token, but not whitespace before it. Probably not worth + * doing our own space-skipping to avoid that. + */ + char *start = _state->lex->input; + int len = _state->lex->prev_token_terminator - start; + + _state->tresult = cstring_to_text_with_len(start, len); + } + } - if (_state->lex->lex_level == 0 && _state->search_type != JSON_SEARCH_PATH) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot extract element from a scalar"))); if (_state->next_scalar) { /* a de-escaped text value is wanted, so supply it */ @@ -1093,17 +1081,17 @@ get_scalar(void *state, char *token, JsonTokenType tokentype) Datum jsonb_extract_path(PG_FUNCTION_ARGS) { - return get_jsonb_path_all(fcinfo, "jsonb_extract_path", false); + return get_jsonb_path_all(fcinfo, false); } Datum jsonb_extract_path_text(PG_FUNCTION_ARGS) { - return get_jsonb_path_all(fcinfo, "jsonb_extract_path_text", true); + return get_jsonb_path_all(fcinfo, true); } static Datum -get_jsonb_path_all(FunctionCallInfo fcinfo, const char *funcname, bool as_text) +get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text) { Jsonb *jb = PG_GETARG_JSONB(0); ArrayType *path = PG_GETARG_ARRAYTYPE_P(1); @@ -1118,28 +1106,56 @@ get_jsonb_path_all(FunctionCallInfo fcinfo, const char *funcname, bool as_text) JsonbValue tv; JsonbContainer *container; + /* + * If the array contains any null elements, return NULL, on the grounds + * that you'd have gotten NULL if any RHS value were NULL in a nested + * series of applications of the -> operator. (Note: because we also + * return NULL for error cases such as no-such-field, this is true + * regardless of the contents of the rest of the array.) + */ if (array_contains_nulls(path)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot call %s with null path elements", - funcname))); + PG_RETURN_NULL(); deconstruct_array(path, TEXTOID, -1, false, 'i', &pathtext, &pathnulls, &npath); - /* - * If the array is empty, return NULL; this is dubious but it's what 9.3 - * did. - */ - if (npath <= 0) - PG_RETURN_NULL(); + /* Identify whether we have object, array, or scalar at top-level */ + container = &jb->root; if (JB_ROOT_IS_OBJECT(jb)) have_object = true; else if (JB_ROOT_IS_ARRAY(jb) && !JB_ROOT_IS_SCALAR(jb)) have_array = true; + else + { + Assert(JB_ROOT_IS_ARRAY(jb) && JB_ROOT_IS_SCALAR(jb)); + /* Extract the scalar value, if it is what we'll return */ + if (npath <= 0) + jbvp = getIthJsonbValueFromContainer(container, 0); + } - container = &jb->root; + /* + * If the array is empty, return the entire LHS object, on the grounds + * that we should do zero field or element extractions. For the + * non-scalar case we can just hand back the object without much work. For + * the scalar case, fall through and deal with the value below the loop. + * (This inconsistency arises because there's no easy way to generate a + * JsonbValue directly for root-level containers.) + */ + if (npath <= 0 && jbvp == NULL) + { + if (as_text) + { + PG_RETURN_TEXT_P(cstring_to_text(JsonbToCString(NULL, + container, + VARSIZE(jb)))); + } + else + { + /* not text mode - just hand back the jsonb */ + PG_RETURN_JSONB(jb); + } + } for (i = 0; i < npath; i++) { @@ -1157,18 +1173,17 @@ get_jsonb_path_all(FunctionCallInfo fcinfo, const char *funcname, bool as_text) char *indextext = TextDatumGetCString(pathtext[i]); char *endptr; + errno = 0; lindex = strtol(indextext, &endptr, 10); - if (*endptr != '\0' || lindex > INT_MAX || lindex < 0) + if (endptr == indextext || *endptr != '\0' || errno != 0 || + lindex > INT_MAX || lindex < 0) PG_RETURN_NULL(); index = (uint32) lindex; jbvp = getIthJsonbValueFromContainer(container, index); } else { - if (i == 0) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot extract path from a scalar"))); + /* scalar, extraction yields a null */ PG_RETURN_NULL(); } @@ -1196,9 +1211,11 @@ get_jsonb_path_all(FunctionCallInfo fcinfo, const char *funcname, bool as_text) if (as_text) { + /* special-case outputs for string and null values */ if (jbvp->type == jbvString) - PG_RETURN_TEXT_P(cstring_to_text_with_len(jbvp->val.string.val, jbvp->val.string.len)); - else if (jbvp->type == jbvNull) + PG_RETURN_TEXT_P(cstring_to_text_with_len(jbvp->val.string.val, + jbvp->val.string.len)); + if (jbvp->type == jbvNull) PG_RETURN_NULL(); } |