Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 1d5f3f9

Browse filesBrowse 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 c336e90 commit 1d5f3f9
Copy full SHA for 1d5f3f9

File tree

Expand file treeCollapse file tree

4 files changed

+86
-51
lines changed
Filter options
Expand file treeCollapse file tree

4 files changed

+86
-51
lines changed

‎doc/src/sgml/func.sgml

Copy file name to clipboardExpand all lines: doc/src/sgml/func.sgml
+7-4Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10896,10 +10896,13 @@ table2-mapping
1089610896

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

‎src/backend/utils/adt/jsonfuncs.c

Copy file name to clipboardExpand all lines: src/backend/utils/adt/jsonfuncs.c
+40-45Lines changed: 40 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3674,36 +3674,39 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
36743674
rk1,
36753675
rk2;
36763676

3677-
r1 = rk1 = JsonbIteratorNext(it1, &v1, false);
3678-
r2 = rk2 = JsonbIteratorNext(it2, &v2, false);
3677+
rk1 = JsonbIteratorNext(it1, &v1, false);
3678+
rk2 = JsonbIteratorNext(it2, &v2, false);
36793679

36803680
/*
3681-
* Both elements are objects.
3681+
* JsonbIteratorNext reports raw scalars as if they were single-element
3682+
* arrays; hence we only need consider "object" and "array" cases here.
36823683
*/
36833684
if (rk1 == WJB_BEGIN_OBJECT && rk2 == WJB_BEGIN_OBJECT)
36843685
{
36853686
/*
3686-
* Append the all tokens from v1 to res, except last WJB_END_OBJECT
3687+
* Both inputs are objects.
3688+
*
3689+
* Append all the tokens from v1 to res, except last WJB_END_OBJECT
36873690
* (because res will not be finished yet).
36883691
*/
3689-
pushJsonbValue(state, r1, NULL);
3692+
pushJsonbValue(state, rk1, NULL);
36903693
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_OBJECT)
36913694
pushJsonbValue(state, r1, &v1);
36923695

36933696
/*
3694-
* Append the all tokens from v2 to res, include last WJB_END_OBJECT
3695-
* (the concatenation will be completed).
3697+
* Append all the tokens from v2 to res, including last WJB_END_OBJECT
3698+
* (the concatenation will be completed). Any duplicate keys will
3699+
* automatically override the value from the first object.
36963700
*/
3697-
while ((r2 = JsonbIteratorNext(it2, &v2, true)) != 0)
3701+
while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE)
36983702
res = pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL);
36993703
}
3700-
3701-
/*
3702-
* Both elements are arrays (either can be scalar).
3703-
*/
37043704
else if (rk1 == WJB_BEGIN_ARRAY && rk2 == WJB_BEGIN_ARRAY)
37053705
{
3706-
pushJsonbValue(state, r1, NULL);
3706+
/*
3707+
* Both inputs are arrays.
3708+
*/
3709+
pushJsonbValue(state, rk1, NULL);
37073710

37083711
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_ARRAY)
37093712
{
@@ -3719,48 +3722,40 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
37193722

37203723
res = pushJsonbValue(state, WJB_END_ARRAY, NULL /* signal to sort */ );
37213724
}
3722-
/* have we got array || object or object || array? */
3723-
else if (((rk1 == WJB_BEGIN_ARRAY && !(*it1)->isScalar) && rk2 == WJB_BEGIN_OBJECT) ||
3724-
(rk1 == WJB_BEGIN_OBJECT && (rk2 == WJB_BEGIN_ARRAY && !(*it2)->isScalar)))
3725+
else if (rk1 == WJB_BEGIN_OBJECT)
37253726
{
3726-
3727-
JsonbIterator **it_array = rk1 == WJB_BEGIN_ARRAY ? it1 : it2;
3728-
JsonbIterator **it_object = rk1 == WJB_BEGIN_OBJECT ? it1 : it2;
3729-
3730-
bool prepend = (rk1 == WJB_BEGIN_OBJECT);
3727+
/*
3728+
* We have object || array.
3729+
*/
3730+
Assert(rk2 == WJB_BEGIN_ARRAY);
37313731

37323732
pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL);
37333733

3734-
if (prepend)
3735-
{
3736-
pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
3737-
while ((r1 = JsonbIteratorNext(it_object, &v1, true)) != 0)
3738-
pushJsonbValue(state, r1, r1 != WJB_END_OBJECT ? &v1 : NULL);
3734+
pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
3735+
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_DONE)
3736+
pushJsonbValue(state, r1, r1 != WJB_END_OBJECT ? &v1 : NULL);
37393737

3740-
while ((r2 = JsonbIteratorNext(it_array, &v2, true)) != 0)
3741-
res = pushJsonbValue(state, r2, r2 != WJB_END_ARRAY ? &v2 : NULL);
3742-
}
3743-
else
3744-
{
3745-
while ((r1 = JsonbIteratorNext(it_array, &v1, true)) != WJB_END_ARRAY)
3746-
pushJsonbValue(state, r1, &v1);
3747-
3748-
pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
3749-
while ((r2 = JsonbIteratorNext(it_object, &v2, true)) != 0)
3750-
pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL);
3751-
3752-
res = pushJsonbValue(state, WJB_END_ARRAY, NULL);
3753-
}
3738+
while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE)
3739+
res = pushJsonbValue(state, r2, r2 != WJB_END_ARRAY ? &v2 : NULL);
37543740
}
37553741
else
37563742
{
37573743
/*
3758-
* This must be scalar || object or object || scalar, as that's all
3759-
* that's left. Both of these make no sense, so error out.
3744+
* We have array || object.
37603745
*/
3761-
ereport(ERROR,
3762-
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
3763-
errmsg("invalid concatenation of jsonb objects")));
3746+
Assert(rk1 == WJB_BEGIN_ARRAY);
3747+
Assert(rk2 == WJB_BEGIN_OBJECT);
3748+
3749+
pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL);
3750+
3751+
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_ARRAY)
3752+
pushJsonbValue(state, r1, &v1);
3753+
3754+
pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
3755+
while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE)
3756+
pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL);
3757+
3758+
res = pushJsonbValue(state, WJB_END_ARRAY, NULL);
37643759
}
37653760

37663761
return res;

‎src/test/regress/expected/jsonb.out

Copy file name to clipboardExpand all lines: src/test/regress/expected/jsonb.out
+34-2Lines 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

Copy file name to clipboardExpand all lines: src/test/regress/sql/jsonb.sql
+5Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.