Skip to content

Commit edcdbc4

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 7414a05 commit edcdbc4

File tree

4 files changed

+86
-51
lines changed

4 files changed

+86
-51
lines changed

doc/src/sgml/func.sgml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10493,10 +10493,13 @@ table2-mapping
1049310493

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

src/backend/utils/adt/jsonfuncs.c

Lines changed: 40 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3628,36 +3628,39 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
36283628
rk1,
36293629
rk2;
36303630

3631-
r1 = rk1 = JsonbIteratorNext(it1, &v1, false);
3632-
r2 = rk2 = JsonbIteratorNext(it2, &v2, false);
3631+
rk1 = JsonbIteratorNext(it1, &v1, false);
3632+
rk2 = JsonbIteratorNext(it2, &v2, false);
36333633

36343634
/*
3635-
* Both elements are objects.
3635+
* JsonbIteratorNext reports raw scalars as if they were single-element
3636+
* arrays; hence we only need consider "object" and "array" cases here.
36363637
*/
36373638
if (rk1 == WJB_BEGIN_OBJECT && rk2 == WJB_BEGIN_OBJECT)
36383639
{
36393640
/*
3640-
* Append the all tokens from v1 to res, except last WJB_END_OBJECT
3641+
* Both inputs are objects.
3642+
*
3643+
* Append all the tokens from v1 to res, except last WJB_END_OBJECT
36413644
* (because res will not be finished yet).
36423645
*/
3643-
pushJsonbValue(state, r1, NULL);
3646+
pushJsonbValue(state, rk1, NULL);
36443647
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_OBJECT)
36453648
pushJsonbValue(state, r1, &v1);
36463649

36473650
/*
3648-
* Append the all tokens from v2 to res, include last WJB_END_OBJECT
3649-
* (the concatenation will be completed).
3651+
* Append all the tokens from v2 to res, including last WJB_END_OBJECT
3652+
* (the concatenation will be completed). Any duplicate keys will
3653+
* automatically override the value from the first object.
36503654
*/
3651-
while ((r2 = JsonbIteratorNext(it2, &v2, true)) != 0)
3655+
while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE)
36523656
res = pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL);
36533657
}
3654-
3655-
/*
3656-
* Both elements are arrays (either can be scalar).
3657-
*/
36583658
else if (rk1 == WJB_BEGIN_ARRAY && rk2 == WJB_BEGIN_ARRAY)
36593659
{
3660-
pushJsonbValue(state, r1, NULL);
3660+
/*
3661+
* Both inputs are arrays.
3662+
*/
3663+
pushJsonbValue(state, rk1, NULL);
36613664

36623665
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_ARRAY)
36633666
{
@@ -3673,48 +3676,40 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
36733676

36743677
res = pushJsonbValue(state, WJB_END_ARRAY, NULL /* signal to sort */ );
36753678
}
3676-
/* have we got array || object or object || array? */
3677-
else if (((rk1 == WJB_BEGIN_ARRAY && !(*it1)->isScalar) && rk2 == WJB_BEGIN_OBJECT) ||
3678-
(rk1 == WJB_BEGIN_OBJECT && (rk2 == WJB_BEGIN_ARRAY && !(*it2)->isScalar)))
3679+
else if (rk1 == WJB_BEGIN_OBJECT)
36793680
{
3680-
3681-
JsonbIterator **it_array = rk1 == WJB_BEGIN_ARRAY ? it1 : it2;
3682-
JsonbIterator **it_object = rk1 == WJB_BEGIN_OBJECT ? it1 : it2;
3683-
3684-
bool prepend = (rk1 == WJB_BEGIN_OBJECT);
3681+
/*
3682+
* We have object || array.
3683+
*/
3684+
Assert(rk2 == WJB_BEGIN_ARRAY);
36853685

36863686
pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL);
36873687

3688-
if (prepend)
3689-
{
3690-
pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
3691-
while ((r1 = JsonbIteratorNext(it_object, &v1, true)) != 0)
3692-
pushJsonbValue(state, r1, r1 != WJB_END_OBJECT ? &v1 : NULL);
3688+
pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
3689+
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_DONE)
3690+
pushJsonbValue(state, r1, r1 != WJB_END_OBJECT ? &v1 : NULL);
36933691

3694-
while ((r2 = JsonbIteratorNext(it_array, &v2, true)) != 0)
3695-
res = pushJsonbValue(state, r2, r2 != WJB_END_ARRAY ? &v2 : NULL);
3696-
}
3697-
else
3698-
{
3699-
while ((r1 = JsonbIteratorNext(it_array, &v1, true)) != WJB_END_ARRAY)
3700-
pushJsonbValue(state, r1, &v1);
3701-
3702-
pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
3703-
while ((r2 = JsonbIteratorNext(it_object, &v2, true)) != 0)
3704-
pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL);
3705-
3706-
res = pushJsonbValue(state, WJB_END_ARRAY, NULL);
3707-
}
3692+
while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE)
3693+
res = pushJsonbValue(state, r2, r2 != WJB_END_ARRAY ? &v2 : NULL);
37083694
}
37093695
else
37103696
{
37113697
/*
3712-
* This must be scalar || object or object || scalar, as that's all
3713-
* that's left. Both of these make no sense, so error out.
3698+
* We have array || object.
37143699
*/
3715-
ereport(ERROR,
3716-
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
3717-
errmsg("invalid concatenation of jsonb objects")));
3700+
Assert(rk1 == WJB_BEGIN_ARRAY);
3701+
Assert(rk2 == WJB_BEGIN_OBJECT);
3702+
3703+
pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL);
3704+
3705+
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_ARRAY)
3706+
pushJsonbValue(state, r1, &v1);
3707+
3708+
pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
3709+
while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE)
3710+
pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL);
3711+
3712+
res = pushJsonbValue(state, WJB_END_ARRAY, NULL);
37183713
}
37193714

37203715
return res;

src/test/regress/expected/jsonb.out

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

30563056
select '"a"'::jsonb || '{"a":1}';
3057-
ERROR: invalid concatenation of jsonb objects
3057+
?column?
3058+
-----------------
3059+
["a", {"a": 1}]
3060+
(1 row)
3061+
30583062
select '{"a":1}' || '"a"'::jsonb;
3059-
ERROR: invalid concatenation of jsonb objects
3063+
?column?
3064+
-----------------
3065+
[{"a": 1}, "a"]
3066+
(1 row)
3067+
3068+
select '[3]'::jsonb || '{}'::jsonb;
3069+
?column?
3070+
----------
3071+
[3, {}]
3072+
(1 row)
3073+
3074+
select '3'::jsonb || '[]'::jsonb;
3075+
?column?
3076+
----------
3077+
[3]
3078+
(1 row)
3079+
3080+
select '3'::jsonb || '4'::jsonb;
3081+
?column?
3082+
----------
3083+
[3, 4]
3084+
(1 row)
3085+
3086+
select '3'::jsonb || '{}'::jsonb;
3087+
?column?
3088+
----------
3089+
[3, {}]
3090+
(1 row)
3091+
30603092
select '["a", "b"]'::jsonb || '{"c":1}';
30613093
?column?
30623094
----------------------

src/test/regress/sql/jsonb.sql

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

775+
select '[3]'::jsonb || '{}'::jsonb;
776+
select '3'::jsonb || '[]'::jsonb;
777+
select '3'::jsonb || '4'::jsonb;
778+
select '3'::jsonb || '{}'::jsonb;
779+
775780
select '["a", "b"]'::jsonb || '{"c":1}';
776781
select '{"c": 1}'::jsonb || '["a", "b"]';
777782

0 commit comments

Comments
 (0)