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:
parent
be9c3cd186
commit
38d30a14b0
@ -14736,8 +14736,12 @@ table2-mapping
|
|||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
Concatenates two <type>jsonb</type> values.
|
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.
|
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
|
Does not operate recursively: only the top-level array or object
|
||||||
structure is merged.
|
structure is merged.
|
||||||
</para>
|
</para>
|
||||||
@ -14748,6 +14752,22 @@ table2-mapping
|
|||||||
<para>
|
<para>
|
||||||
<literal>'{"a": "b"}'::jsonb || '{"c": "d"}'::jsonb</literal>
|
<literal>'{"a": "b"}'::jsonb || '{"c": "d"}'::jsonb</literal>
|
||||||
<returnvalue>{"a": "b", "c": "d"}</returnvalue>
|
<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>
|
</para></entry>
|
||||||
</row>
|
</row>
|
||||||
|
|
||||||
|
@ -4687,36 +4687,39 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
|
|||||||
rk1,
|
rk1,
|
||||||
rk2;
|
rk2;
|
||||||
|
|
||||||
r1 = rk1 = JsonbIteratorNext(it1, &v1, false);
|
rk1 = JsonbIteratorNext(it1, &v1, false);
|
||||||
r2 = rk2 = JsonbIteratorNext(it2, &v2, false);
|
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)
|
if (rk1 == WJB_BEGIN_OBJECT && rk2 == WJB_BEGIN_OBJECT)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Append the all tokens from v1 to res, except last WJB_END_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).
|
* (because res will not be finished yet).
|
||||||
*/
|
*/
|
||||||
pushJsonbValue(state, r1, NULL);
|
pushJsonbValue(state, rk1, NULL);
|
||||||
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_OBJECT)
|
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_OBJECT)
|
||||||
pushJsonbValue(state, r1, &v1);
|
pushJsonbValue(state, r1, &v1);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Append the all tokens from v2 to res, include last WJB_END_OBJECT
|
* Append all the tokens from v2 to res, including last WJB_END_OBJECT
|
||||||
* (the concatenation will be completed).
|
* (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)
|
while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE)
|
||||||
res = pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL);
|
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)
|
else if (rk1 == WJB_BEGIN_ARRAY && rk2 == WJB_BEGIN_ARRAY)
|
||||||
{
|
{
|
||||||
pushJsonbValue(state, r1, NULL);
|
/*
|
||||||
|
* Both inputs are arrays.
|
||||||
|
*/
|
||||||
|
pushJsonbValue(state, rk1, NULL);
|
||||||
|
|
||||||
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_ARRAY)
|
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_ARRAY)
|
||||||
{
|
{
|
||||||
@ -4732,49 +4735,41 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
|
|||||||
|
|
||||||
res = pushJsonbValue(state, WJB_END_ARRAY, NULL /* signal to sort */ );
|
res = pushJsonbValue(state, WJB_END_ARRAY, NULL /* signal to sort */ );
|
||||||
}
|
}
|
||||||
/* have we got array || object or object || array? */
|
else if (rk1 == WJB_BEGIN_OBJECT)
|
||||||
else if (((rk1 == WJB_BEGIN_ARRAY && !(*it1)->isScalar) && rk2 == WJB_BEGIN_OBJECT) ||
|
|
||||||
(rk1 == WJB_BEGIN_OBJECT && (rk2 == WJB_BEGIN_ARRAY && !(*it2)->isScalar)))
|
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
JsonbIterator **it_array = rk1 == WJB_BEGIN_ARRAY ? it1 : it2;
|
* We have object || array.
|
||||||
JsonbIterator **it_object = rk1 == WJB_BEGIN_OBJECT ? it1 : it2;
|
*/
|
||||||
|
Assert(rk2 == WJB_BEGIN_ARRAY);
|
||||||
bool prepend = (rk1 == WJB_BEGIN_OBJECT);
|
|
||||||
|
|
||||||
pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL);
|
pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL);
|
||||||
|
|
||||||
if (prepend)
|
|
||||||
{
|
|
||||||
pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
|
pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
|
||||||
while ((r1 = JsonbIteratorNext(it_object, &v1, true)) != WJB_DONE)
|
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_DONE)
|
||||||
pushJsonbValue(state, r1, r1 != WJB_END_OBJECT ? &v1 : NULL);
|
pushJsonbValue(state, r1, r1 != WJB_END_OBJECT ? &v1 : NULL);
|
||||||
|
|
||||||
while ((r2 = JsonbIteratorNext(it_array, &v2, true)) != WJB_DONE)
|
while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE)
|
||||||
res = pushJsonbValue(state, r2, r2 != WJB_END_ARRAY ? &v2 : NULL);
|
res = pushJsonbValue(state, r2, r2 != WJB_END_ARRAY ? &v2 : NULL);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
while ((r1 = JsonbIteratorNext(it_array, &v1, true)) != WJB_END_ARRAY)
|
/*
|
||||||
|
* We have array || object.
|
||||||
|
*/
|
||||||
|
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, r1, &v1);
|
||||||
|
|
||||||
pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
|
pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
|
||||||
while ((r2 = JsonbIteratorNext(it_object, &v2, true)) != WJB_DONE)
|
while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE)
|
||||||
pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL);
|
pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL);
|
||||||
|
|
||||||
res = pushJsonbValue(state, WJB_END_ARRAY, NULL);
|
res = pushJsonbValue(state, WJB_END_ARRAY, 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.
|
|
||||||
*/
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
||||||
errmsg("invalid concatenation of jsonb objects")));
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -4111,9 +4111,41 @@ select '{"a":"b"}'::jsonb || '[]'::jsonb;
|
|||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
select '"a"'::jsonb || '{"a":1}';
|
select '"a"'::jsonb || '{"a":1}';
|
||||||
ERROR: invalid concatenation of jsonb objects
|
?column?
|
||||||
|
-----------------
|
||||||
|
["a", {"a": 1}]
|
||||||
|
(1 row)
|
||||||
|
|
||||||
select '{"a":1}' || '"a"'::jsonb;
|
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}';
|
select '["a", "b"]'::jsonb || '{"c":1}';
|
||||||
?column?
|
?column?
|
||||||
----------------------
|
----------------------
|
||||||
|
@ -1056,6 +1056,11 @@ select '{"a":"b"}'::jsonb || '[]'::jsonb;
|
|||||||
select '"a"'::jsonb || '{"a":1}';
|
select '"a"'::jsonb || '{"a":1}';
|
||||||
select '{"a":1}' || '"a"'::jsonb;
|
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 '["a", "b"]'::jsonb || '{"c":1}';
|
||||||
select '{"c": 1}'::jsonb || '["a", "b"]';
|
select '{"c": 1}'::jsonb || '["a", "b"]';
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user