aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/jsonfuncs.c
diff options
context:
space:
mode:
authorAndrew Dunstan <andrew@dunslane.net>2015-07-17 20:56:13 -0400
committerAndrew Dunstan <andrew@dunslane.net>2015-07-17 21:13:47 -0400
commite02d44b8a74810341c90add4cd49e428b9d406b9 (patch)
treec3eaa2e67f602ea8f258c8cd45c2c3e7706e7ec1 /src/backend/utils/adt/jsonfuncs.c
parent0fc94a5bab4d0155db5d15197ed3bd8cb435eb21 (diff)
downloadpostgresql-e02d44b8a74810341c90add4cd49e428b9d406b9.tar.gz
postgresql-e02d44b8a74810341c90add4cd49e428b9d406b9.zip
Support JSON negative array subscripts everywhere
Previously, there was an inconsistency across json/jsonb operators that operate on datums containing JSON arrays -- only some operators supported negative array count-from-the-end subscripting. Specifically, only a new-to-9.5 jsonb deletion operator had support (the new "jsonb - integer" operator). This inconsistency seemed likely to be counter-intuitive to users. To fix, allow all places where the user can supply an integer subscript to accept a negative subscript value, including path-orientated operators and functions, as well as other extraction operators. This will need to be called out as an incompatibility in the 9.5 release notes, since it's possible that users are relying on certain established extraction operators changed here yielding NULL in the event of a negative subscript. For the json type, this requires adding a way of cheaply getting the total JSON array element count ahead of time when parsing arrays with a negative subscript involved, necessitating an ad-hoc lex and parse. This is followed by a "conversion" from a negative subscript to its equivalent positive-wise value using the count. From there on, it's as if a positive-wise value was originally provided. Note that there is still a minor inconsistency here across jsonb deletion operators. Unlike the aforementioned new "-" deletion operator that accepts an integer on its right hand side, the new "#-" path orientated deletion variant does not throw an error when it appears like an array subscript (input that could be recognized by as an integer literal) is being used on an object, which is wrong-headed. The reason for not being stricter is that it could be the case that an object pair happens to have a key value that looks like an integer; in general, these two possibilities are impossible to differentiate with rhs path text[] argument elements. However, we still don't allow the "#-" path-orientated deletion operator to perform array-style subscripting. Rather, we just return the original left operand value in the event of a negative subscript (which seems analogous to how the established "jsonb/json #> text[]" path-orientated operator may yield NULL in the event of an invalid subscript). In passing, make SetArrayPath() stricter about not accepting cases where there is trailing non-numeric garbage bytes rather than a clean NUL byte. This means, for example, that strings like "10e10" are now not accepted as an array subscript of 10 by some new-to-9.5 path-orientated jsonb operators (e.g. the new #- operator). Finally, remove dead code for jsonb subscript deletion; arguably, this should have been done in commit b81c7b409. Peter Geoghegan and Andrew Dunstan
Diffstat (limited to 'src/backend/utils/adt/jsonfuncs.c')
-rw-r--r--src/backend/utils/adt/jsonfuncs.c100
1 files changed, 75 insertions, 25 deletions
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 13d5b7af2f4..424280b929e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -597,6 +597,17 @@ jsonb_array_element(PG_FUNCTION_ARGS)
if (!JB_ROOT_IS_ARRAY(jb))
PG_RETURN_NULL();
+ /* Handle negative subscript */
+ if (element < 0)
+ {
+ uint32 nelements = JB_ROOT_COUNT(jb);
+
+ if (-element > nelements)
+ PG_RETURN_NULL();
+ else
+ element += nelements;
+ }
+
v = getIthJsonbValueFromContainer(&jb->root, element);
if (v != NULL)
PG_RETURN_JSONB(JsonbValueToJsonb(v));
@@ -629,6 +640,17 @@ jsonb_array_element_text(PG_FUNCTION_ARGS)
if (!JB_ROOT_IS_ARRAY(jb))
PG_RETURN_NULL();
+ /* Handle negative subscript */
+ if (element < 0)
+ {
+ uint32 nelements = JB_ROOT_COUNT(jb);
+
+ if (-element > nelements)
+ PG_RETURN_NULL();
+ else
+ element += nelements;
+ }
+
v = getIthJsonbValueFromContainer(&jb->root, element);
if (v != NULL)
{
@@ -719,7 +741,7 @@ get_path_all(FunctionCallInfo fcinfo, bool as_text)
/*
* 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.
+ * all the other integers to INT_MIN which will never match.
*/
if (*tpath[i] != '\0')
{
@@ -728,13 +750,13 @@ get_path_all(FunctionCallInfo fcinfo, bool as_text)
errno = 0;
ind = strtol(tpath[i], &endptr, 10);
- if (*endptr == '\0' && errno == 0 && ind <= INT_MAX && ind >= 0)
+ if (*endptr == '\0' && errno == 0 && ind <= INT_MAX && ind >= INT_MIN)
ipath[i] = (int) ind;
else
- ipath[i] = -1;
+ ipath[i] = INT_MIN;
}
else
- ipath[i] = -1;
+ ipath[i] = INT_MIN;
}
result = get_worker(json, tpath, ipath, npath, as_text);
@@ -752,14 +774,15 @@ get_path_all(FunctionCallInfo fcinfo, bool as_text)
*
* json: JSON object (in text form)
* tpath[]: field name(s) to extract
- * ipath[]: array index(es) (zero-based) to extract
+ * ipath[]: array index(es) (zero-based) to extract, accepts negatives
* 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.
+ * be NULL, or any one ipath[] entry can be INT_MIN if an array element is
+ * not to be matched at that nesting level (a json datum should never be
+ * large enough to have -INT_MIN elements due to MaxAllocSize restriction).
*/
static text *
get_worker(text *json,
@@ -964,6 +987,17 @@ get_array_start(void *state)
*/
_state->result_start = _state->lex->token_start;
}
+
+ /* INT_MIN value is reserved to represent invalid subscript */
+ if (_state->path_indexes[lex_level] < 0 &&
+ _state->path_indexes[lex_level] != INT_MIN)
+ {
+ /* Negative subscript -- convert to positive-wise subscript */
+ int nelements = json_count_array_elements(_state->lex);
+
+ if (-_state->path_indexes[lex_level] <= nelements)
+ _state->path_indexes[lex_level] += nelements;
+ }
}
static void
@@ -1209,9 +1243,30 @@ get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text)
errno = 0;
lindex = strtol(indextext, &endptr, 10);
if (endptr == indextext || *endptr != '\0' || errno != 0 ||
- lindex > INT_MAX || lindex < 0)
+ lindex > INT_MAX || lindex < INT_MIN)
PG_RETURN_NULL();
- index = (uint32) lindex;
+
+ if (lindex >= 0)
+ {
+ index = (uint32) lindex;
+ }
+ else
+ {
+ /* Handle negative subscript */
+ uint32 nelements;
+
+ /* Container must be array, but make sure */
+ if ((container->header & JB_FARRAY) == 0)
+ elog(ERROR, "not a jsonb array");
+
+ nelements = container->header & JB_CMASK;
+
+ if (-lindex > nelements)
+ PG_RETURN_NULL();
+ else
+ index = nelements + lindex;
+ }
+
jbvp = getIthJsonbValueFromContainer(container, index);
}
else
@@ -3411,10 +3466,8 @@ jsonb_delete_idx(PG_FUNCTION_ARGS)
it = JsonbIteratorInit(&in->root);
r = JsonbIteratorNext(&it, &v, false);
- if (r == WJB_BEGIN_ARRAY)
- n = v.val.array.nElems;
- else
- n = v.val.object.nPairs;
+ Assert (r == WJB_BEGIN_ARRAY);
+ n = v.val.array.nElems;
if (idx < 0)
{
@@ -3431,14 +3484,10 @@ jsonb_delete_idx(PG_FUNCTION_ARGS)
while ((r = JsonbIteratorNext(&it, &v, true)) != 0)
{
- if (r == WJB_ELEM || r == WJB_KEY)
+ if (r == WJB_ELEM)
{
if (i++ == idx)
- {
- if (r == WJB_KEY)
- JsonbIteratorNext(&it, &v, true); /* skip value */
continue;
- }
}
res = pushJsonbValue(&state, r, r < WJB_BEGIN_ARRAY ? &v : NULL);
@@ -3657,7 +3706,7 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
* If newval is null, the element is to be removed.
*
* If create is true, we create the new value if the key or array index
- * does not exist. All path elemnts before the last must already exist
+ * does not exist. All path elements before the last must already exist
* whether or not create is true, or nothing is done.
*/
static JsonbValue *
@@ -3818,7 +3867,8 @@ setPathArray(JsonbIterator **it, Datum *path_elems, bool *path_nulls,
errno = 0;
lindex = strtol(c, &badp, 10);
- if (errno != 0 || badp == c || lindex > INT_MAX || lindex < INT_MIN)
+ if (errno != 0 || badp == c || *badp != '\0' || lindex > INT_MAX ||
+ lindex < INT_MIN)
idx = nelems;
else
idx = lindex;
@@ -3829,7 +3879,7 @@ setPathArray(JsonbIterator **it, Datum *path_elems, bool *path_nulls,
if (idx < 0)
{
if (-idx > nelems)
- idx = -1;
+ idx = INT_MIN;
else
idx = nelems + idx;
}
@@ -3838,12 +3888,12 @@ setPathArray(JsonbIterator **it, Datum *path_elems, bool *path_nulls,
idx = nelems;
/*
- * if we're creating, and idx == -1, we prepend the new value to the array
- * also if the array is empty - in which case we don't really care what
- * the idx value is
+ * if we're creating, and idx == INT_MIN, we prepend the new value to the
+ * array also if the array is empty - in which case we don't really care
+ * what the idx value is
*/
- if ((idx == -1 || nelems == 0) && create && (level == path_len - 1))
+ if ((idx == INT_MIN || nelems == 0) && create && (level == path_len - 1))
{
Assert(newval != NULL);
addJsonbToParseState(st, newval);