Skip to content

Commit 38d30a1

Browse files
committed
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
1 parent be9c3cd commit 38d30a1

File tree

4 files changed

+99
-47
lines changed

4 files changed

+99
-47
lines changed

doc/src/sgml/func.sgml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14736,8 +14736,12 @@ table2-mapping
1473614736
</para>
1473714737
<para>
1473814738
Concatenates two <type>jsonb</type> values.
14739-
Concatenating two objects generates an object with the union of their
14739+
Concatenating two arrays generates an array containing all the
14740+
elements of each input. Concatenating two objects generates an
14741+
object containing the union of their
1474014742
keys, taking the second object's value when there are duplicate keys.
14743+
All other cases are treated by converting a non-array input into a
14744+
single-element array, and then proceeding as for two arrays.
1474114745
Does not operate recursively: only the top-level array or object
1474214746
structure is merged.
1474314747
</para>
@@ -14748,6 +14752,22 @@ table2-mapping
1474814752
<para>
1474914753
<literal>'{"a": "b"}'::jsonb || '{"c": "d"}'::jsonb</literal>
1475014754
<returnvalue>{"a": "b", "c": "d"}</returnvalue>
14755+
</para>
14756+
<para>
14757+
<literal>'[1, 2]'::jsonb || '3'::jsonb</literal>
14758+
<returnvalue>[1, 2, 3]</returnvalue>
14759+
</para>
14760+
<para>
14761+
<literal>'{"a": "b"}'::jsonb || '42'::jsonb</literal>
14762+
<returnvalue>[{"a": "b"}, 42]</returnvalue>
14763+
</para>
14764+
<para>
14765+
To append an array to another array as a single entry, wrap it
14766+
in an additional layer of array, for example:
14767+
</para>
14768+
<para>
14769+
<literal>'[1, 2]'::jsonb || jsonb_build_array('[3, 4]'::jsonb)</literal>
14770+
<returnvalue>[1, 2, [3, 4]]</returnvalue>
1475114771
</para></entry>
1475214772
</row>
1475314773

src/backend/utils/adt/jsonfuncs.c

Lines changed: 39 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4687,36 +4687,39 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
46874687
rk1,
46884688
rk2;
46894689

4690-
r1 = rk1 = JsonbIteratorNext(it1, &v1, false);
4691-
r2 = rk2 = JsonbIteratorNext(it2, &v2, false);
4690+
rk1 = JsonbIteratorNext(it1, &v1, false);
4691+
rk2 = JsonbIteratorNext(it2, &v2, false);
46924692

46934693
/*
4694-
* Both elements are objects.
4694+
* JsonbIteratorNext reports raw scalars as if they were single-element
4695+
* arrays; hence we only need consider "object" and "array" cases here.
46954696
*/
46964697
if (rk1 == WJB_BEGIN_OBJECT && rk2 == WJB_BEGIN_OBJECT)
46974698
{
46984699
/*
4699-
* Append the all tokens from v1 to res, except last WJB_END_OBJECT
4700+
* Both inputs are objects.
4701+
*
4702+
* Append all the tokens from v1 to res, except last WJB_END_OBJECT
47004703
* (because res will not be finished yet).
47014704
*/
4702-
pushJsonbValue(state, r1, NULL);
4705+
pushJsonbValue(state, rk1, NULL);
47034706
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_OBJECT)
47044707
pushJsonbValue(state, r1, &v1);
47054708

47064709
/*
4707-
* Append the all tokens from v2 to res, include last WJB_END_OBJECT
4708-
* (the concatenation will be completed).
4710+
* Append all the tokens from v2 to res, including last WJB_END_OBJECT
4711+
* (the concatenation will be completed). Any duplicate keys will
4712+
* automatically override the value from the first object.
47094713
*/
47104714
while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE)
47114715
res = pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL);
47124716
}
4713-
4714-
/*
4715-
* Both elements are arrays (either can be scalar).
4716-
*/
47174717
else if (rk1 == WJB_BEGIN_ARRAY && rk2 == WJB_BEGIN_ARRAY)
47184718
{
4719-
pushJsonbValue(state, r1, NULL);
4719+
/*
4720+
* Both inputs are arrays.
4721+
*/
4722+
pushJsonbValue(state, rk1, NULL);
47204723

47214724
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_ARRAY)
47224725
{
@@ -4732,48 +4735,40 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
47324735

47334736
res = pushJsonbValue(state, WJB_END_ARRAY, NULL /* signal to sort */ );
47344737
}
4735-
/* have we got array || object or object || array? */
4736-
else if (((rk1 == WJB_BEGIN_ARRAY && !(*it1)->isScalar) && rk2 == WJB_BEGIN_OBJECT) ||
4737-
(rk1 == WJB_BEGIN_OBJECT && (rk2 == WJB_BEGIN_ARRAY && !(*it2)->isScalar)))
4738+
else if (rk1 == WJB_BEGIN_OBJECT)
47384739
{
4739-
4740-
JsonbIterator **it_array = rk1 == WJB_BEGIN_ARRAY ? it1 : it2;
4741-
JsonbIterator **it_object = rk1 == WJB_BEGIN_OBJECT ? it1 : it2;
4742-
4743-
bool prepend = (rk1 == WJB_BEGIN_OBJECT);
4740+
/*
4741+
* We have object || array.
4742+
*/
4743+
Assert(rk2 == WJB_BEGIN_ARRAY);
47444744

47454745
pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL);
47464746

4747-
if (prepend)
4748-
{
4749-
pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
4750-
while ((r1 = JsonbIteratorNext(it_object, &v1, true)) != WJB_DONE)
4751-
pushJsonbValue(state, r1, r1 != WJB_END_OBJECT ? &v1 : NULL);
4752-
4753-
while ((r2 = JsonbIteratorNext(it_array, &v2, true)) != WJB_DONE)
4754-
res = pushJsonbValue(state, r2, r2 != WJB_END_ARRAY ? &v2 : NULL);
4755-
}
4756-
else
4757-
{
4758-
while ((r1 = JsonbIteratorNext(it_array, &v1, true)) != WJB_END_ARRAY)
4759-
pushJsonbValue(state, r1, &v1);
4747+
pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
4748+
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_DONE)
4749+
pushJsonbValue(state, r1, r1 != WJB_END_OBJECT ? &v1 : NULL);
47604750

4761-
pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
4762-
while ((r2 = JsonbIteratorNext(it_object, &v2, true)) != WJB_DONE)
4763-
pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL);
4764-
4765-
res = pushJsonbValue(state, WJB_END_ARRAY, NULL);
4766-
}
4751+
while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE)
4752+
res = pushJsonbValue(state, r2, r2 != WJB_END_ARRAY ? &v2 : NULL);
47674753
}
47684754
else
47694755
{
47704756
/*
4771-
* This must be scalar || object or object || scalar, as that's all
4772-
* that's left. Both of these make no sense, so error out.
4757+
* We have array || object.
47734758
*/
4774-
ereport(ERROR,
4775-
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4776-
errmsg("invalid concatenation of jsonb objects")));
4759+
Assert(rk1 == WJB_BEGIN_ARRAY);
4760+
Assert(rk2 == WJB_BEGIN_OBJECT);
4761+
4762+
pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL);
4763+
4764+
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_ARRAY)
4765+
pushJsonbValue(state, r1, &v1);
4766+
4767+
pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
4768+
while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE)
4769+
pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL);
4770+
4771+
res = pushJsonbValue(state, WJB_END_ARRAY, NULL);
47774772
}
47784773

47794774
return res;

src/test/regress/expected/jsonb.out

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4111,9 +4111,41 @@ select '{"a":"b"}'::jsonb || '[]'::jsonb;
41114111
(1 row)
41124112

41134113
select '"a"'::jsonb || '{"a":1}';
4114-
ERROR: invalid concatenation of jsonb objects
4114+
?column?
4115+
-----------------
4116+
["a", {"a": 1}]
4117+
(1 row)
4118+
41154119
select '{"a":1}' || '"a"'::jsonb;
4116-
ERROR: invalid concatenation of jsonb objects
4120+
?column?
4121+
-----------------
4122+
[{"a": 1}, "a"]
4123+
(1 row)
4124+
4125+
select '[3]'::jsonb || '{}'::jsonb;
4126+
?column?
4127+
----------
4128+
[3, {}]
4129+
(1 row)
4130+
4131+
select '3'::jsonb || '[]'::jsonb;
4132+
?column?
4133+
----------
4134+
[3]
4135+
(1 row)
4136+
4137+
select '3'::jsonb || '4'::jsonb;
4138+
?column?
4139+
----------
4140+
[3, 4]
4141+
(1 row)
4142+
4143+
select '3'::jsonb || '{}'::jsonb;
4144+
?column?
4145+
----------
4146+
[3, {}]
4147+
(1 row)
4148+
41174149
select '["a", "b"]'::jsonb || '{"c":1}';
41184150
?column?
41194151
----------------------

src/test/regress/sql/jsonb.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,6 +1056,11 @@ select '{"a":"b"}'::jsonb || '[]'::jsonb;
10561056
select '"a"'::jsonb || '{"a":1}';
10571057
select '{"a":1}' || '"a"'::jsonb;
10581058

1059+
select '[3]'::jsonb || '{}'::jsonb;
1060+
select '3'::jsonb || '[]'::jsonb;
1061+
select '3'::jsonb || '4'::jsonb;
1062+
select '3'::jsonb || '{}'::jsonb;
1063+
10591064
select '["a", "b"]'::jsonb || '{"c":1}';
10601065
select '{"c": 1}'::jsonb || '["a", "b"]';
10611066

0 commit comments

Comments
 (0)