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 b6efd8a6da
commit 75c8ef5ae5
4 changed files with 85 additions and 50 deletions

View File

@ -11455,10 +11455,13 @@ table2-mapping
<note> <note>
<para> <para>
The <literal>||</literal> operator concatenates the elements at the top level of The <literal>||</literal> operator concatenates two JSON objects by
each of its operands. It does not operate recursively. For example, if generating an object containing the union of their keys, taking the
both operands are objects with a common key field name, the value of the second object's value when there are duplicate keys. All other cases
field in the result will just be the value from the right hand operand. produce a JSON array: first, any non-array input is converted into a
single-element array, and then the two arrays are concatenated.
It does not operate recursively; only the top-level array or object
structure is merged.
</para> </para>
</note> </note>

View File

@ -4583,36 +4583,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)
{ {
@ -4628,49 +4631,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;
} }

View File

@ -3654,9 +3654,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?
---------------------- ----------------------

View File

@ -973,6 +973,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"]';