Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit ff5d561

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 86b7cca commit ff5d561

File tree

4 files changed

+94
-40
lines changed

4 files changed

+94
-40
lines changed

doc/src/sgml/func.sgml

+21-1
Original file line numberDiff line numberDiff line change
@@ -14715,8 +14715,12 @@ table2-mapping
1471514715
</para>
1471614716
<para>
1471714717
Concatenates two <type>jsonb</type> values.
14718-
Concatenating two objects generates an object with the union of their
14718+
Concatenating two arrays generates an array containing all the
14719+
elements of each input. Concatenating two objects generates an
14720+
object containing the union of their
1471914721
keys, taking the second object's value when there are duplicate keys.
14722+
All other cases are treated by converting a non-array input into a
14723+
single-element array, and then proceeding as for two arrays.
1472014724
Does not operate recursively: only the top-level array or object
1472114725
structure is merged.
1472214726
</para>
@@ -14727,6 +14731,22 @@ table2-mapping
1472714731
<para>
1472814732
<literal>'{"a": "b"}'::jsonb || '{"c": "d"}'::jsonb</literal>
1472914733
<returnvalue>{"a": "b", "c": "d"}</returnvalue>
14734+
</para>
14735+
<para>
14736+
<literal>'[1, 2]'::jsonb || '3'::jsonb</literal>
14737+
<returnvalue>[1, 2, 3]</returnvalue>
14738+
</para>
14739+
<para>
14740+
<literal>'{"a": "b"}'::jsonb || '42'::jsonb</literal>
14741+
<returnvalue>[{"a": "b"}, 42]</returnvalue>
14742+
</para>
14743+
<para>
14744+
To append an array to another array as a single entry, wrap it
14745+
in an additional layer of array, for example:
14746+
</para>
14747+
<para>
14748+
<literal>'[1, 2]'::jsonb || jsonb_build_array('[3, 4]'::jsonb)</literal>
14749+
<returnvalue>[1, 2, [3, 4]]</returnvalue>
1473014750
</para></entry>
1473114751
</row>
1473214752

src/backend/utils/adt/jsonfuncs.c

+34-37
Original file line numberDiff line numberDiff line change
@@ -4690,11 +4690,14 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
46904690
rk2 = JsonbIteratorNext(it2, &v2, false);
46914691

46924692
/*
4693-
* Both elements are objects.
4693+
* JsonbIteratorNext reports raw scalars as if they were single-element
4694+
* arrays; hence we only need consider "object" and "array" cases here.
46944695
*/
46954696
if (rk1 == WJB_BEGIN_OBJECT && rk2 == WJB_BEGIN_OBJECT)
46964697
{
46974698
/*
4699+
* Both inputs are objects.
4700+
*
46984701
* Append all the tokens from v1 to res, except last WJB_END_OBJECT
46994702
* (because res will not be finished yet).
47004703
*/
@@ -4703,18 +4706,18 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
47034706
pushJsonbValue(state, r1, &v1);
47044707

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

47204723
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_ARRAY)
@@ -4731,46 +4734,40 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
47314734

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

47424744
pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL);
47434745

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

4762-
res = pushJsonbValue(state, WJB_END_ARRAY, NULL);
4763-
}
4750+
while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE)
4751+
res = pushJsonbValue(state, r2, r2 != WJB_END_ARRAY ? &v2 : NULL);
47644752
}
47654753
else
47664754
{
47674755
/*
4768-
* This must be scalar || object or object || scalar, as that's all
4769-
* that's left. Both of these make no sense, so error out.
4756+
* We have array || object.
47704757
*/
4771-
ereport(ERROR,
4772-
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4773-
errmsg("invalid concatenation of jsonb objects")));
4758+
Assert(rk1 == WJB_BEGIN_ARRAY);
4759+
Assert(rk2 == WJB_BEGIN_OBJECT);
4760+
4761+
pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL);
4762+
4763+
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_ARRAY)
4764+
pushJsonbValue(state, r1, &v1);
4765+
4766+
pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
4767+
while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE)
4768+
pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL);
4769+
4770+
res = pushJsonbValue(state, WJB_END_ARRAY, NULL);
47744771
}
47754772

47764773
return res;

src/test/regress/expected/jsonb.out

+34-2
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

+5
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)