aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/jsonfuncs.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2014-08-22 13:17:58 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2014-08-22 13:17:58 -0400
commit41dd50e84df39e31595f3472b0cb6d00f63b3f99 (patch)
tree34d4d479d154d440d7f8ed6e0d43905af9917e55 /src/backend/utils/adt/jsonfuncs.c
parentebf20f65d9383166d4b883258c091b245cf36217 (diff)
downloadpostgresql-41dd50e84df39e31595f3472b0cb6d00f63b3f99.tar.gz
postgresql-41dd50e84df39e31595f3472b0cb6d00f63b3f99.zip
Fix corner-case behaviors in JSON/JSONB field extraction operators.
Cause the path extraction operators to return their lefthand input, not NULL, if the path array has no elements. This seems more consistent since the case ought to correspond to applying the simple extraction operator (->) zero times. Cause other corner cases in field/element/path extraction to return NULL rather than failing. This behavior is arguably more useful than throwing an error, since it allows an expression index using these operators to be built even when not all values in the column are suitable for the extraction being indexed. Moreover, we already had multiple inconsistencies between the path extraction operators and the simple extraction operators, as well as inconsistencies between the JSON and JSONB code paths. Adopt a uniform rule of returning NULL rather than throwing an error when the JSON input does not have a structure that permits the request to be satisfied. Back-patch to 9.4. Update the release notes to list this as a behavior change since 9.3.
Diffstat (limited to 'src/backend/utils/adt/jsonfuncs.c')
-rw-r--r--src/backend/utils/adt/jsonfuncs.c575
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();
}