Remove "invalid concatenation of jsonb objects" error case.

The jsonb || jsonb operator arbitrarily rejected certain combinations
of scalar and non-scalar inputs, while being willing to concatenate
other combinations.  This was of course quite undocumented.  Rather
than trying to document it, let's just remove the restriction,
creating a uniform rule that unless we are handling an object-to-object
concatenation, non-array inputs are converted to one-element arrays,
resulting in an array-to-array concatenation.  (This does not change
the behavior for any case that didn't throw an error before.)

Per complaint from Joel Jacobson.  Back-patch to all supported branches.

Discussion: https://postgr.es/m/163099.1608312033@sss.pgh.pa.us
This commit is contained in:
Tom Lane 2020-12-21 13:11:29 -05:00
parent 86b7cca72d
commit ff5d5611c0
4 changed files with 94 additions and 40 deletions

View File

@ -14715,8 +14715,12 @@ table2-mapping
</para>
<para>
Concatenates two <type>jsonb</type> values.
Concatenating two objects generates an object with the union of their
Concatenating two arrays generates an array containing all the
elements of each input. Concatenating two objects generates an
object containing the union of their
keys, taking the second object's value when there are duplicate keys.
All other cases are treated by converting a non-array input into a
single-element array, and then proceeding as for two arrays.
Does not operate recursively: only the top-level array or object
structure is merged.
</para>
@ -14727,6 +14731,22 @@ table2-mapping
<para>
<literal>'{"a": "b"}'::jsonb || '{"c": "d"}'::jsonb</literal>
<returnvalue>{"a": "b", "c": "d"}</returnvalue>
</para>
<para>
<literal>'[1, 2]'::jsonb || '3'::jsonb</literal>
<returnvalue>[1, 2, 3]</returnvalue>
</para>
<para>
<literal>'{"a": "b"}'::jsonb || '42'::jsonb</literal>
<returnvalue>[{"a": "b"}, 42]</returnvalue>
</para>
<para>
To append an array to another array as a single entry, wrap it
in an additional layer of array, for example:
</para>
<para>
<literal>'[1, 2]'::jsonb || jsonb_build_array('[3, 4]'::jsonb)</literal>
<returnvalue>[1, 2, [3, 4]]</returnvalue>
</para></entry>
</row>

View File

@ -4690,11 +4690,14 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
rk2 = JsonbIteratorNext(it2, &v2, false);
/*
* Both elements are objects.
* JsonbIteratorNext reports raw scalars as if they were single-element
* arrays; hence we only need consider "object" and "array" cases here.
*/
if (rk1 == WJB_BEGIN_OBJECT && rk2 == WJB_BEGIN_OBJECT)
{
/*
* Both inputs are objects.
*
* Append all the tokens from v1 to res, except last WJB_END_OBJECT
* (because res will not be finished yet).
*/
@ -4703,18 +4706,18 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
pushJsonbValue(state, r1, &v1);
/*
* Append all the tokens from v2 to res, include last WJB_END_OBJECT
* (the concatenation will be completed).
* Append all the tokens from v2 to res, including last WJB_END_OBJECT
* (the concatenation will be completed). Any duplicate keys will
* automatically override the value from the first object.
*/
while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE)
res = pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL);
}
/*
* Both elements are arrays (either can be scalar).
*/
else if (rk1 == WJB_BEGIN_ARRAY && rk2 == WJB_BEGIN_ARRAY)
{
/*
* Both inputs are arrays.
*/
pushJsonbValue(state, rk1, NULL);
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_ARRAY)
@ -4731,46 +4734,40 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
res = pushJsonbValue(state, WJB_END_ARRAY, NULL /* signal to sort */ );
}
/* have we got array || object or object || array? */
else if (((rk1 == WJB_BEGIN_ARRAY && !(*it1)->isScalar) && rk2 == WJB_BEGIN_OBJECT) ||
(rk1 == WJB_BEGIN_OBJECT && (rk2 == WJB_BEGIN_ARRAY && !(*it2)->isScalar)))
else if (rk1 == WJB_BEGIN_OBJECT)
{
JsonbIterator **it_array = rk1 == WJB_BEGIN_ARRAY ? it1 : it2;
JsonbIterator **it_object = rk1 == WJB_BEGIN_OBJECT ? it1 : it2;
bool prepend = (rk1 == WJB_BEGIN_OBJECT);
/*
* We have object || array.
*/
Assert(rk2 == WJB_BEGIN_ARRAY);
pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL);
if (prepend)
{
pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
while ((r1 = JsonbIteratorNext(it_object, &v1, true)) != WJB_DONE)
pushJsonbValue(state, r1, r1 != WJB_END_OBJECT ? &v1 : NULL);
pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_DONE)
pushJsonbValue(state, r1, r1 != WJB_END_OBJECT ? &v1 : NULL);
while ((r2 = JsonbIteratorNext(it_array, &v2, true)) != WJB_DONE)
res = pushJsonbValue(state, r2, r2 != WJB_END_ARRAY ? &v2 : NULL);
}
else
{
while ((r1 = JsonbIteratorNext(it_array, &v1, true)) != WJB_END_ARRAY)
pushJsonbValue(state, r1, &v1);
pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
while ((r2 = JsonbIteratorNext(it_object, &v2, true)) != WJB_DONE)
pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL);
res = pushJsonbValue(state, WJB_END_ARRAY, NULL);
}
while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE)
res = pushJsonbValue(state, r2, r2 != WJB_END_ARRAY ? &v2 : NULL);
}
else
{
/*
* This must be scalar || object or object || scalar, as that's all
* that's left. Both of these make no sense, so error out.
* We have array || object.
*/
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid concatenation of jsonb objects")));
Assert(rk1 == WJB_BEGIN_ARRAY);
Assert(rk2 == WJB_BEGIN_OBJECT);
pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL);
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_ARRAY)
pushJsonbValue(state, r1, &v1);
pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE)
pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL);
res = pushJsonbValue(state, WJB_END_ARRAY, NULL);
}
return res;

View File

@ -4111,9 +4111,41 @@ select '{"a":"b"}'::jsonb || '[]'::jsonb;
(1 row)
select '"a"'::jsonb || '{"a":1}';
ERROR: invalid concatenation of jsonb objects
?column?
-----------------
["a", {"a": 1}]
(1 row)
select '{"a":1}' || '"a"'::jsonb;
ERROR: invalid concatenation of jsonb objects
?column?
-----------------
[{"a": 1}, "a"]
(1 row)
select '[3]'::jsonb || '{}'::jsonb;
?column?
----------
[3, {}]
(1 row)
select '3'::jsonb || '[]'::jsonb;
?column?
----------
[3]
(1 row)
select '3'::jsonb || '4'::jsonb;
?column?
----------
[3, 4]
(1 row)
select '3'::jsonb || '{}'::jsonb;
?column?
----------
[3, {}]
(1 row)
select '["a", "b"]'::jsonb || '{"c":1}';
?column?
----------------------

View File

@ -1056,6 +1056,11 @@ select '{"a":"b"}'::jsonb || '[]'::jsonb;
select '"a"'::jsonb || '{"a":1}';
select '{"a":1}' || '"a"'::jsonb;
select '[3]'::jsonb || '{}'::jsonb;
select '3'::jsonb || '[]'::jsonb;
select '3'::jsonb || '4'::jsonb;
select '3'::jsonb || '{}'::jsonb;
select '["a", "b"]'::jsonb || '{"c":1}';
select '{"c": 1}'::jsonb || '["a", "b"]';