aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/src/sgml/func.sgml11
-rw-r--r--src/backend/utils/adt/jsonfuncs.c83
-rw-r--r--src/test/regress/expected/jsonb.out36
-rw-r--r--src/test/regress/sql/jsonb.sql5
4 files changed, 85 insertions, 50 deletions
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c123b7fc828..b7289d01d31 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -11455,10 +11455,13 @@ table2-mapping
<note>
<para>
- The <literal>||</literal> operator concatenates the elements at the top level of
- each of its operands. It does not operate recursively. For example, if
- both operands are objects with a common key field name, the value of the
- field in the result will just be the value from the right hand operand.
+ The <literal>||</literal> operator concatenates two JSON objects by
+ generating an object containing the union of their keys, taking the
+ second object's value when there are duplicate keys. All other cases
+ 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>
</note>
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 787bc54a9ec..860c46abdf0 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4583,36 +4583,39 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
rk1,
rk2;
- r1 = rk1 = JsonbIteratorNext(it1, &v1, false);
- r2 = rk2 = JsonbIteratorNext(it2, &v2, false);
+ rk1 = JsonbIteratorNext(it1, &v1, 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)
{
/*
- * 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).
*/
- pushJsonbValue(state, r1, NULL);
+ pushJsonbValue(state, rk1, NULL);
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_OBJECT)
pushJsonbValue(state, r1, &v1);
/*
- * Append the all tokens from v2 to res, include last WJB_END_OBJECT
- * (the concatenation will be completed).
+ * Append all the tokens from v2 to res, including last WJB_END_OBJECT
+ * (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)
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)
{
- pushJsonbValue(state, r1, NULL);
+ /*
+ * Both inputs are arrays.
+ */
+ pushJsonbValue(state, rk1, NULL);
while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_ARRAY)
{
@@ -4628,48 +4631,40 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
res = pushJsonbValue(state, WJB_END_ARRAY, NULL /* signal to sort */ );
}
- /* have we got array || object or object || array? */
- else if (((rk1 == WJB_BEGIN_ARRAY && !(*it1)->isScalar) && rk2 == WJB_BEGIN_OBJECT) ||
- (rk1 == WJB_BEGIN_OBJECT && (rk2 == WJB_BEGIN_ARRAY && !(*it2)->isScalar)))
+ else if (rk1 == WJB_BEGIN_OBJECT)
{
-
- JsonbIterator **it_array = rk1 == WJB_BEGIN_ARRAY ? it1 : it2;
- JsonbIterator **it_object = rk1 == WJB_BEGIN_OBJECT ? it1 : it2;
-
- bool prepend = (rk1 == WJB_BEGIN_OBJECT);
+ /*
+ * We have object || array.
+ */
+ Assert(rk2 == WJB_BEGIN_ARRAY);
pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL);
- if (prepend)
- {
- pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
- while ((r1 = JsonbIteratorNext(it_object, &v1, true)) != WJB_DONE)
- pushJsonbValue(state, r1, r1 != WJB_END_OBJECT ? &v1 : NULL);
-
- while ((r2 = JsonbIteratorNext(it_array, &v2, true)) != WJB_DONE)
- res = pushJsonbValue(state, r2, r2 != WJB_END_ARRAY ? &v2 : NULL);
- }
- else
- {
- while ((r1 = JsonbIteratorNext(it_array, &v1, true)) != WJB_END_ARRAY)
- pushJsonbValue(state, r1, &v1);
+ pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
+ while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_DONE)
+ pushJsonbValue(state, r1, r1 != WJB_END_OBJECT ? &v1 : NULL);
- pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL);
- while ((r2 = JsonbIteratorNext(it_object, &v2, true)) != WJB_DONE)
- pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL);
-
- res = pushJsonbValue(state, WJB_END_ARRAY, NULL);
- }
+ while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE)
+ res = pushJsonbValue(state, r2, r2 != WJB_END_ARRAY ? &v2 : 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.
+ * We have array || object.
*/
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid concatenation of jsonb objects")));
+ 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, WJB_BEGIN_OBJECT, NULL);
+ while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE)
+ pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL);
+
+ res = pushJsonbValue(state, WJB_END_ARRAY, NULL);
}
return res;
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index bd6fc2e47dd..e9f22a4279d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -3654,9 +3654,41 @@ select '{"a":"b"}'::jsonb || '[]'::jsonb;
(1 row)
select '"a"'::jsonb || '{"a":1}';
-ERROR: invalid concatenation of jsonb objects
+ ?column?
+-----------------
+ ["a", {"a": 1}]
+(1 row)
+
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}';
?column?
----------------------
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index a1831adf33d..06c29168938 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -973,6 +973,11 @@ select '{"a":"b"}'::jsonb || '[]'::jsonb;
select '"a"'::jsonb || '{"a":1}';
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 '{"c": 1}'::jsonb || '["a", "b"]';