public inbox for [email protected]
help / color / mirror / Atom feed[PATCH v45 3/3] Replace assuming a composite object on a scalar
108+ messages / 3 participants
[nested] [flat]
* [PATCH v44 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
3 files changed, 31 insertions(+)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--i5uwshohlpbxyckd--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v44 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
3 files changed, 31 insertions(+)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--i5uwshohlpbxyckd--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v45 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 22 ++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 9af015d222..924762e128 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -612,8 +612,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
<literal>path</literal> argument in <literal>jsonb_set</literal> function,
e.g. in case of arrays it is a 0-based operation or that negative integers
that appear in <literal>path</literal> count from the end of JSON arrays.
- The result of subscripting expressions is always jsonb data type. An
- example of subscripting syntax:
+ The result of subscripting expressions is always jsonb data type.
+
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Every
+ affected value must conform to the path defined by the subscript(s). If the
+ path contradicts structure of modified <type>jsonb</type> for any individual
+ value (e.g. path <literal>val['a']['b']['c']</literal> assumes keys
+ <literal>'a'</literal> and <literal>'b'</literal> have object values
+ assigned to them, but if <literal>val['a']</literal> or
+ <literal>val['b']</literal> is null, a string, or a number, then the path
+ contradicts with the existing structure), an error is raised even if other
+ values do conform.
+ </para>
+ <para>
+
+ An example of subscripting syntax:
<programlisting>
-- Extract value by key
SELECT ('{"a": 1}'::jsonb)['a'];
@@ -628,6 +643,9 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- needs to be of jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if jsonb_field is {"a": 1}
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Select records using where clause with subscripting. Since the result of
-- subscripting is jsonb and we basically want to compare two jsonb objects, we
-- need to put the value in double quotes to be able to convert it to jsonb.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--2v7pubbjx3p2om6u--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v44 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
3 files changed, 31 insertions(+)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--i5uwshohlpbxyckd--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v45 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 22 ++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 9af015d222..924762e128 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -612,8 +612,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
<literal>path</literal> argument in <literal>jsonb_set</literal> function,
e.g. in case of arrays it is a 0-based operation or that negative integers
that appear in <literal>path</literal> count from the end of JSON arrays.
- The result of subscripting expressions is always jsonb data type. An
- example of subscripting syntax:
+ The result of subscripting expressions is always jsonb data type.
+
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Every
+ affected value must conform to the path defined by the subscript(s). If the
+ path contradicts structure of modified <type>jsonb</type> for any individual
+ value (e.g. path <literal>val['a']['b']['c']</literal> assumes keys
+ <literal>'a'</literal> and <literal>'b'</literal> have object values
+ assigned to them, but if <literal>val['a']</literal> or
+ <literal>val['b']</literal> is null, a string, or a number, then the path
+ contradicts with the existing structure), an error is raised even if other
+ values do conform.
+ </para>
+ <para>
+
+ An example of subscripting syntax:
<programlisting>
-- Extract value by key
SELECT ('{"a": 1}'::jsonb)['a'];
@@ -628,6 +643,9 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- needs to be of jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if jsonb_field is {"a": 1}
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Select records using where clause with subscripting. Since the result of
-- subscripting is jsonb and we basically want to compare two jsonb objects, we
-- need to put the value in double quotes to be able to convert it to jsonb.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--2v7pubbjx3p2om6u--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v44 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
3 files changed, 31 insertions(+)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--i5uwshohlpbxyckd--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v45 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 22 ++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 9af015d222..924762e128 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -612,8 +612,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
<literal>path</literal> argument in <literal>jsonb_set</literal> function,
e.g. in case of arrays it is a 0-based operation or that negative integers
that appear in <literal>path</literal> count from the end of JSON arrays.
- The result of subscripting expressions is always jsonb data type. An
- example of subscripting syntax:
+ The result of subscripting expressions is always jsonb data type.
+
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Every
+ affected value must conform to the path defined by the subscript(s). If the
+ path contradicts structure of modified <type>jsonb</type> for any individual
+ value (e.g. path <literal>val['a']['b']['c']</literal> assumes keys
+ <literal>'a'</literal> and <literal>'b'</literal> have object values
+ assigned to them, but if <literal>val['a']</literal> or
+ <literal>val['b']</literal> is null, a string, or a number, then the path
+ contradicts with the existing structure), an error is raised even if other
+ values do conform.
+ </para>
+ <para>
+
+ An example of subscripting syntax:
<programlisting>
-- Extract value by key
SELECT ('{"a": 1}'::jsonb)['a'];
@@ -628,6 +643,9 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- needs to be of jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if jsonb_field is {"a": 1}
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Select records using where clause with subscripting. Since the result of
-- subscripting is jsonb and we basically want to compare two jsonb objects, we
-- need to put the value in double quotes to be able to convert it to jsonb.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--2v7pubbjx3p2om6u--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v45 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 22 ++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 9af015d222..924762e128 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -612,8 +612,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
<literal>path</literal> argument in <literal>jsonb_set</literal> function,
e.g. in case of arrays it is a 0-based operation or that negative integers
that appear in <literal>path</literal> count from the end of JSON arrays.
- The result of subscripting expressions is always jsonb data type. An
- example of subscripting syntax:
+ The result of subscripting expressions is always jsonb data type.
+
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Every
+ affected value must conform to the path defined by the subscript(s). If the
+ path contradicts structure of modified <type>jsonb</type> for any individual
+ value (e.g. path <literal>val['a']['b']['c']</literal> assumes keys
+ <literal>'a'</literal> and <literal>'b'</literal> have object values
+ assigned to them, but if <literal>val['a']</literal> or
+ <literal>val['b']</literal> is null, a string, or a number, then the path
+ contradicts with the existing structure), an error is raised even if other
+ values do conform.
+ </para>
+ <para>
+
+ An example of subscripting syntax:
<programlisting>
-- Extract value by key
SELECT ('{"a": 1}'::jsonb)['a'];
@@ -628,6 +643,9 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- needs to be of jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if jsonb_field is {"a": 1}
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Select records using where clause with subscripting. Since the result of
-- subscripting is jsonb and we basically want to compare two jsonb objects, we
-- need to put the value in double quotes to be able to convert it to jsonb.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--2v7pubbjx3p2om6u--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v45 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 22 ++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 9af015d222..924762e128 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -612,8 +612,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
<literal>path</literal> argument in <literal>jsonb_set</literal> function,
e.g. in case of arrays it is a 0-based operation or that negative integers
that appear in <literal>path</literal> count from the end of JSON arrays.
- The result of subscripting expressions is always jsonb data type. An
- example of subscripting syntax:
+ The result of subscripting expressions is always jsonb data type.
+
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Every
+ affected value must conform to the path defined by the subscript(s). If the
+ path contradicts structure of modified <type>jsonb</type> for any individual
+ value (e.g. path <literal>val['a']['b']['c']</literal> assumes keys
+ <literal>'a'</literal> and <literal>'b'</literal> have object values
+ assigned to them, but if <literal>val['a']</literal> or
+ <literal>val['b']</literal> is null, a string, or a number, then the path
+ contradicts with the existing structure), an error is raised even if other
+ values do conform.
+ </para>
+ <para>
+
+ An example of subscripting syntax:
<programlisting>
-- Extract value by key
SELECT ('{"a": 1}'::jsonb)['a'];
@@ -628,6 +643,9 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- needs to be of jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if jsonb_field is {"a": 1}
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Select records using where clause with subscripting. Since the result of
-- subscripting is jsonb and we basically want to compare two jsonb objects, we
-- need to put the value in double quotes to be able to convert it to jsonb.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--2v7pubbjx3p2om6u--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v44 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
3 files changed, 31 insertions(+)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--i5uwshohlpbxyckd--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v45 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 22 ++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 9af015d222..924762e128 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -612,8 +612,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
<literal>path</literal> argument in <literal>jsonb_set</literal> function,
e.g. in case of arrays it is a 0-based operation or that negative integers
that appear in <literal>path</literal> count from the end of JSON arrays.
- The result of subscripting expressions is always jsonb data type. An
- example of subscripting syntax:
+ The result of subscripting expressions is always jsonb data type.
+
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Every
+ affected value must conform to the path defined by the subscript(s). If the
+ path contradicts structure of modified <type>jsonb</type> for any individual
+ value (e.g. path <literal>val['a']['b']['c']</literal> assumes keys
+ <literal>'a'</literal> and <literal>'b'</literal> have object values
+ assigned to them, but if <literal>val['a']</literal> or
+ <literal>val['b']</literal> is null, a string, or a number, then the path
+ contradicts with the existing structure), an error is raised even if other
+ values do conform.
+ </para>
+ <para>
+
+ An example of subscripting syntax:
<programlisting>
-- Extract value by key
SELECT ('{"a": 1}'::jsonb)['a'];
@@ -628,6 +643,9 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- needs to be of jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if jsonb_field is {"a": 1}
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Select records using where clause with subscripting. Since the result of
-- subscripting is jsonb and we basically want to compare two jsonb objects, we
-- need to put the value in double quotes to be able to convert it to jsonb.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--2v7pubbjx3p2om6u--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v44 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
3 files changed, 31 insertions(+)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--i5uwshohlpbxyckd--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v44 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
3 files changed, 31 insertions(+)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--i5uwshohlpbxyckd--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v44 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
3 files changed, 31 insertions(+)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--i5uwshohlpbxyckd--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v45 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 22 ++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 9af015d222..924762e128 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -612,8 +612,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
<literal>path</literal> argument in <literal>jsonb_set</literal> function,
e.g. in case of arrays it is a 0-based operation or that negative integers
that appear in <literal>path</literal> count from the end of JSON arrays.
- The result of subscripting expressions is always jsonb data type. An
- example of subscripting syntax:
+ The result of subscripting expressions is always jsonb data type.
+
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Every
+ affected value must conform to the path defined by the subscript(s). If the
+ path contradicts structure of modified <type>jsonb</type> for any individual
+ value (e.g. path <literal>val['a']['b']['c']</literal> assumes keys
+ <literal>'a'</literal> and <literal>'b'</literal> have object values
+ assigned to them, but if <literal>val['a']</literal> or
+ <literal>val['b']</literal> is null, a string, or a number, then the path
+ contradicts with the existing structure), an error is raised even if other
+ values do conform.
+ </para>
+ <para>
+
+ An example of subscripting syntax:
<programlisting>
-- Extract value by key
SELECT ('{"a": 1}'::jsonb)['a'];
@@ -628,6 +643,9 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- needs to be of jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if jsonb_field is {"a": 1}
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Select records using where clause with subscripting. Since the result of
-- subscripting is jsonb and we basically want to compare two jsonb objects, we
-- need to put the value in double quotes to be able to convert it to jsonb.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--2v7pubbjx3p2om6u--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v44 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
3 files changed, 31 insertions(+)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--i5uwshohlpbxyckd--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v45 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 22 ++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 9af015d222..924762e128 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -612,8 +612,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
<literal>path</literal> argument in <literal>jsonb_set</literal> function,
e.g. in case of arrays it is a 0-based operation or that negative integers
that appear in <literal>path</literal> count from the end of JSON arrays.
- The result of subscripting expressions is always jsonb data type. An
- example of subscripting syntax:
+ The result of subscripting expressions is always jsonb data type.
+
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Every
+ affected value must conform to the path defined by the subscript(s). If the
+ path contradicts structure of modified <type>jsonb</type> for any individual
+ value (e.g. path <literal>val['a']['b']['c']</literal> assumes keys
+ <literal>'a'</literal> and <literal>'b'</literal> have object values
+ assigned to them, but if <literal>val['a']</literal> or
+ <literal>val['b']</literal> is null, a string, or a number, then the path
+ contradicts with the existing structure), an error is raised even if other
+ values do conform.
+ </para>
+ <para>
+
+ An example of subscripting syntax:
<programlisting>
-- Extract value by key
SELECT ('{"a": 1}'::jsonb)['a'];
@@ -628,6 +643,9 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- needs to be of jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if jsonb_field is {"a": 1}
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Select records using where clause with subscripting. Since the result of
-- subscripting is jsonb and we basically want to compare two jsonb objects, we
-- need to put the value in double quotes to be able to convert it to jsonb.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--2v7pubbjx3p2om6u--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v44 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
3 files changed, 31 insertions(+)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--i5uwshohlpbxyckd--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v45 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 22 ++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 9af015d222..924762e128 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -612,8 +612,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
<literal>path</literal> argument in <literal>jsonb_set</literal> function,
e.g. in case of arrays it is a 0-based operation or that negative integers
that appear in <literal>path</literal> count from the end of JSON arrays.
- The result of subscripting expressions is always jsonb data type. An
- example of subscripting syntax:
+ The result of subscripting expressions is always jsonb data type.
+
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Every
+ affected value must conform to the path defined by the subscript(s). If the
+ path contradicts structure of modified <type>jsonb</type> for any individual
+ value (e.g. path <literal>val['a']['b']['c']</literal> assumes keys
+ <literal>'a'</literal> and <literal>'b'</literal> have object values
+ assigned to them, but if <literal>val['a']</literal> or
+ <literal>val['b']</literal> is null, a string, or a number, then the path
+ contradicts with the existing structure), an error is raised even if other
+ values do conform.
+ </para>
+ <para>
+
+ An example of subscripting syntax:
<programlisting>
-- Extract value by key
SELECT ('{"a": 1}'::jsonb)['a'];
@@ -628,6 +643,9 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- needs to be of jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if jsonb_field is {"a": 1}
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Select records using where clause with subscripting. Since the result of
-- subscripting is jsonb and we basically want to compare two jsonb objects, we
-- need to put the value in double quotes to be able to convert it to jsonb.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--2v7pubbjx3p2om6u--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v45 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 22 ++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 9af015d222..924762e128 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -612,8 +612,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
<literal>path</literal> argument in <literal>jsonb_set</literal> function,
e.g. in case of arrays it is a 0-based operation or that negative integers
that appear in <literal>path</literal> count from the end of JSON arrays.
- The result of subscripting expressions is always jsonb data type. An
- example of subscripting syntax:
+ The result of subscripting expressions is always jsonb data type.
+
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Every
+ affected value must conform to the path defined by the subscript(s). If the
+ path contradicts structure of modified <type>jsonb</type> for any individual
+ value (e.g. path <literal>val['a']['b']['c']</literal> assumes keys
+ <literal>'a'</literal> and <literal>'b'</literal> have object values
+ assigned to them, but if <literal>val['a']</literal> or
+ <literal>val['b']</literal> is null, a string, or a number, then the path
+ contradicts with the existing structure), an error is raised even if other
+ values do conform.
+ </para>
+ <para>
+
+ An example of subscripting syntax:
<programlisting>
-- Extract value by key
SELECT ('{"a": 1}'::jsonb)['a'];
@@ -628,6 +643,9 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- needs to be of jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if jsonb_field is {"a": 1}
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Select records using where clause with subscripting. Since the result of
-- subscripting is jsonb and we basically want to compare two jsonb objects, we
-- need to put the value in double quotes to be able to convert it to jsonb.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--2v7pubbjx3p2om6u--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v44 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
3 files changed, 31 insertions(+)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--i5uwshohlpbxyckd--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v45 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 22 ++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 9af015d222..924762e128 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -612,8 +612,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
<literal>path</literal> argument in <literal>jsonb_set</literal> function,
e.g. in case of arrays it is a 0-based operation or that negative integers
that appear in <literal>path</literal> count from the end of JSON arrays.
- The result of subscripting expressions is always jsonb data type. An
- example of subscripting syntax:
+ The result of subscripting expressions is always jsonb data type.
+
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Every
+ affected value must conform to the path defined by the subscript(s). If the
+ path contradicts structure of modified <type>jsonb</type> for any individual
+ value (e.g. path <literal>val['a']['b']['c']</literal> assumes keys
+ <literal>'a'</literal> and <literal>'b'</literal> have object values
+ assigned to them, but if <literal>val['a']</literal> or
+ <literal>val['b']</literal> is null, a string, or a number, then the path
+ contradicts with the existing structure), an error is raised even if other
+ values do conform.
+ </para>
+ <para>
+
+ An example of subscripting syntax:
<programlisting>
-- Extract value by key
SELECT ('{"a": 1}'::jsonb)['a'];
@@ -628,6 +643,9 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- needs to be of jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if jsonb_field is {"a": 1}
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Select records using where clause with subscripting. Since the result of
-- subscripting is jsonb and we basically want to compare two jsonb objects, we
-- need to put the value in double quotes to be able to convert it to jsonb.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--2v7pubbjx3p2om6u--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v44 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
3 files changed, 31 insertions(+)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--i5uwshohlpbxyckd--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v45 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 22 ++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 9af015d222..924762e128 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -612,8 +612,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
<literal>path</literal> argument in <literal>jsonb_set</literal> function,
e.g. in case of arrays it is a 0-based operation or that negative integers
that appear in <literal>path</literal> count from the end of JSON arrays.
- The result of subscripting expressions is always jsonb data type. An
- example of subscripting syntax:
+ The result of subscripting expressions is always jsonb data type.
+
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Every
+ affected value must conform to the path defined by the subscript(s). If the
+ path contradicts structure of modified <type>jsonb</type> for any individual
+ value (e.g. path <literal>val['a']['b']['c']</literal> assumes keys
+ <literal>'a'</literal> and <literal>'b'</literal> have object values
+ assigned to them, but if <literal>val['a']</literal> or
+ <literal>val['b']</literal> is null, a string, or a number, then the path
+ contradicts with the existing structure), an error is raised even if other
+ values do conform.
+ </para>
+ <para>
+
+ An example of subscripting syntax:
<programlisting>
-- Extract value by key
SELECT ('{"a": 1}'::jsonb)['a'];
@@ -628,6 +643,9 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- needs to be of jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if jsonb_field is {"a": 1}
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Select records using where clause with subscripting. Since the result of
-- subscripting is jsonb and we basically want to compare two jsonb objects, we
-- need to put the value in double quotes to be able to convert it to jsonb.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--2v7pubbjx3p2om6u--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v45 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 22 ++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 9af015d222..924762e128 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -612,8 +612,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
<literal>path</literal> argument in <literal>jsonb_set</literal> function,
e.g. in case of arrays it is a 0-based operation or that negative integers
that appear in <literal>path</literal> count from the end of JSON arrays.
- The result of subscripting expressions is always jsonb data type. An
- example of subscripting syntax:
+ The result of subscripting expressions is always jsonb data type.
+
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Every
+ affected value must conform to the path defined by the subscript(s). If the
+ path contradicts structure of modified <type>jsonb</type> for any individual
+ value (e.g. path <literal>val['a']['b']['c']</literal> assumes keys
+ <literal>'a'</literal> and <literal>'b'</literal> have object values
+ assigned to them, but if <literal>val['a']</literal> or
+ <literal>val['b']</literal> is null, a string, or a number, then the path
+ contradicts with the existing structure), an error is raised even if other
+ values do conform.
+ </para>
+ <para>
+
+ An example of subscripting syntax:
<programlisting>
-- Extract value by key
SELECT ('{"a": 1}'::jsonb)['a'];
@@ -628,6 +643,9 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- needs to be of jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if jsonb_field is {"a": 1}
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Select records using where clause with subscripting. Since the result of
-- subscripting is jsonb and we basically want to compare two jsonb objects, we
-- need to put the value in double quotes to be able to convert it to jsonb.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--2v7pubbjx3p2om6u--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v44 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
3 files changed, 31 insertions(+)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--i5uwshohlpbxyckd--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v45 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 22 ++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 9af015d222..924762e128 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -612,8 +612,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
<literal>path</literal> argument in <literal>jsonb_set</literal> function,
e.g. in case of arrays it is a 0-based operation or that negative integers
that appear in <literal>path</literal> count from the end of JSON arrays.
- The result of subscripting expressions is always jsonb data type. An
- example of subscripting syntax:
+ The result of subscripting expressions is always jsonb data type.
+
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Every
+ affected value must conform to the path defined by the subscript(s). If the
+ path contradicts structure of modified <type>jsonb</type> for any individual
+ value (e.g. path <literal>val['a']['b']['c']</literal> assumes keys
+ <literal>'a'</literal> and <literal>'b'</literal> have object values
+ assigned to them, but if <literal>val['a']</literal> or
+ <literal>val['b']</literal> is null, a string, or a number, then the path
+ contradicts with the existing structure), an error is raised even if other
+ values do conform.
+ </para>
+ <para>
+
+ An example of subscripting syntax:
<programlisting>
-- Extract value by key
SELECT ('{"a": 1}'::jsonb)['a'];
@@ -628,6 +643,9 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- needs to be of jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if jsonb_field is {"a": 1}
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Select records using where clause with subscripting. Since the result of
-- subscripting is jsonb and we basically want to compare two jsonb objects, we
-- need to put the value in double quotes to be able to convert it to jsonb.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--2v7pubbjx3p2om6u--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v44 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
3 files changed, 31 insertions(+)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--i5uwshohlpbxyckd--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v45 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 22 ++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 9af015d222..924762e128 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -612,8 +612,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
<literal>path</literal> argument in <literal>jsonb_set</literal> function,
e.g. in case of arrays it is a 0-based operation or that negative integers
that appear in <literal>path</literal> count from the end of JSON arrays.
- The result of subscripting expressions is always jsonb data type. An
- example of subscripting syntax:
+ The result of subscripting expressions is always jsonb data type.
+
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Every
+ affected value must conform to the path defined by the subscript(s). If the
+ path contradicts structure of modified <type>jsonb</type> for any individual
+ value (e.g. path <literal>val['a']['b']['c']</literal> assumes keys
+ <literal>'a'</literal> and <literal>'b'</literal> have object values
+ assigned to them, but if <literal>val['a']</literal> or
+ <literal>val['b']</literal> is null, a string, or a number, then the path
+ contradicts with the existing structure), an error is raised even if other
+ values do conform.
+ </para>
+ <para>
+
+ An example of subscripting syntax:
<programlisting>
-- Extract value by key
SELECT ('{"a": 1}'::jsonb)['a'];
@@ -628,6 +643,9 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- needs to be of jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if jsonb_field is {"a": 1}
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Select records using where clause with subscripting. Since the result of
-- subscripting is jsonb and we basically want to compare two jsonb objects, we
-- need to put the value in double quotes to be able to convert it to jsonb.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--2v7pubbjx3p2om6u--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v44 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
3 files changed, 31 insertions(+)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--i5uwshohlpbxyckd--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v44 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
3 files changed, 31 insertions(+)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--i5uwshohlpbxyckd--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v45 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 22 ++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 9af015d222..924762e128 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -612,8 +612,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
<literal>path</literal> argument in <literal>jsonb_set</literal> function,
e.g. in case of arrays it is a 0-based operation or that negative integers
that appear in <literal>path</literal> count from the end of JSON arrays.
- The result of subscripting expressions is always jsonb data type. An
- example of subscripting syntax:
+ The result of subscripting expressions is always jsonb data type.
+
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Every
+ affected value must conform to the path defined by the subscript(s). If the
+ path contradicts structure of modified <type>jsonb</type> for any individual
+ value (e.g. path <literal>val['a']['b']['c']</literal> assumes keys
+ <literal>'a'</literal> and <literal>'b'</literal> have object values
+ assigned to them, but if <literal>val['a']</literal> or
+ <literal>val['b']</literal> is null, a string, or a number, then the path
+ contradicts with the existing structure), an error is raised even if other
+ values do conform.
+ </para>
+ <para>
+
+ An example of subscripting syntax:
<programlisting>
-- Extract value by key
SELECT ('{"a": 1}'::jsonb)['a'];
@@ -628,6 +643,9 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- needs to be of jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if jsonb_field is {"a": 1}
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Select records using where clause with subscripting. Since the result of
-- subscripting is jsonb and we basically want to compare two jsonb objects, we
-- need to put the value in double quotes to be able to convert it to jsonb.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--2v7pubbjx3p2om6u--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v44 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
3 files changed, 31 insertions(+)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--i5uwshohlpbxyckd--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v44 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
3 files changed, 31 insertions(+)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--i5uwshohlpbxyckd--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v45 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 22 ++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 9af015d222..924762e128 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -612,8 +612,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
<literal>path</literal> argument in <literal>jsonb_set</literal> function,
e.g. in case of arrays it is a 0-based operation or that negative integers
that appear in <literal>path</literal> count from the end of JSON arrays.
- The result of subscripting expressions is always jsonb data type. An
- example of subscripting syntax:
+ The result of subscripting expressions is always jsonb data type.
+
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Every
+ affected value must conform to the path defined by the subscript(s). If the
+ path contradicts structure of modified <type>jsonb</type> for any individual
+ value (e.g. path <literal>val['a']['b']['c']</literal> assumes keys
+ <literal>'a'</literal> and <literal>'b'</literal> have object values
+ assigned to them, but if <literal>val['a']</literal> or
+ <literal>val['b']</literal> is null, a string, or a number, then the path
+ contradicts with the existing structure), an error is raised even if other
+ values do conform.
+ </para>
+ <para>
+
+ An example of subscripting syntax:
<programlisting>
-- Extract value by key
SELECT ('{"a": 1}'::jsonb)['a'];
@@ -628,6 +643,9 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- needs to be of jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if jsonb_field is {"a": 1}
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Select records using where clause with subscripting. Since the result of
-- subscripting is jsonb and we basically want to compare two jsonb objects, we
-- need to put the value in double quotes to be able to convert it to jsonb.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--2v7pubbjx3p2om6u--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v45 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 22 ++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 9af015d222..924762e128 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -612,8 +612,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
<literal>path</literal> argument in <literal>jsonb_set</literal> function,
e.g. in case of arrays it is a 0-based operation or that negative integers
that appear in <literal>path</literal> count from the end of JSON arrays.
- The result of subscripting expressions is always jsonb data type. An
- example of subscripting syntax:
+ The result of subscripting expressions is always jsonb data type.
+
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Every
+ affected value must conform to the path defined by the subscript(s). If the
+ path contradicts structure of modified <type>jsonb</type> for any individual
+ value (e.g. path <literal>val['a']['b']['c']</literal> assumes keys
+ <literal>'a'</literal> and <literal>'b'</literal> have object values
+ assigned to them, but if <literal>val['a']</literal> or
+ <literal>val['b']</literal> is null, a string, or a number, then the path
+ contradicts with the existing structure), an error is raised even if other
+ values do conform.
+ </para>
+ <para>
+
+ An example of subscripting syntax:
<programlisting>
-- Extract value by key
SELECT ('{"a": 1}'::jsonb)['a'];
@@ -628,6 +643,9 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- needs to be of jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if jsonb_field is {"a": 1}
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Select records using where clause with subscripting. Since the result of
-- subscripting is jsonb and we basically want to compare two jsonb objects, we
-- need to put the value in double quotes to be able to convert it to jsonb.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--2v7pubbjx3p2om6u--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v44 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
3 files changed, 31 insertions(+)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--i5uwshohlpbxyckd--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v44 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
3 files changed, 31 insertions(+)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--i5uwshohlpbxyckd--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v45 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 22 ++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 9af015d222..924762e128 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -612,8 +612,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
<literal>path</literal> argument in <literal>jsonb_set</literal> function,
e.g. in case of arrays it is a 0-based operation or that negative integers
that appear in <literal>path</literal> count from the end of JSON arrays.
- The result of subscripting expressions is always jsonb data type. An
- example of subscripting syntax:
+ The result of subscripting expressions is always jsonb data type.
+
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Every
+ affected value must conform to the path defined by the subscript(s). If the
+ path contradicts structure of modified <type>jsonb</type> for any individual
+ value (e.g. path <literal>val['a']['b']['c']</literal> assumes keys
+ <literal>'a'</literal> and <literal>'b'</literal> have object values
+ assigned to them, but if <literal>val['a']</literal> or
+ <literal>val['b']</literal> is null, a string, or a number, then the path
+ contradicts with the existing structure), an error is raised even if other
+ values do conform.
+ </para>
+ <para>
+
+ An example of subscripting syntax:
<programlisting>
-- Extract value by key
SELECT ('{"a": 1}'::jsonb)['a'];
@@ -628,6 +643,9 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- needs to be of jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if jsonb_field is {"a": 1}
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Select records using where clause with subscripting. Since the result of
-- subscripting is jsonb and we basically want to compare two jsonb objects, we
-- need to put the value in double quotes to be able to convert it to jsonb.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--2v7pubbjx3p2om6u--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v45 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 22 ++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 9af015d222..924762e128 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -612,8 +612,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
<literal>path</literal> argument in <literal>jsonb_set</literal> function,
e.g. in case of arrays it is a 0-based operation or that negative integers
that appear in <literal>path</literal> count from the end of JSON arrays.
- The result of subscripting expressions is always jsonb data type. An
- example of subscripting syntax:
+ The result of subscripting expressions is always jsonb data type.
+
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Every
+ affected value must conform to the path defined by the subscript(s). If the
+ path contradicts structure of modified <type>jsonb</type> for any individual
+ value (e.g. path <literal>val['a']['b']['c']</literal> assumes keys
+ <literal>'a'</literal> and <literal>'b'</literal> have object values
+ assigned to them, but if <literal>val['a']</literal> or
+ <literal>val['b']</literal> is null, a string, or a number, then the path
+ contradicts with the existing structure), an error is raised even if other
+ values do conform.
+ </para>
+ <para>
+
+ An example of subscripting syntax:
<programlisting>
-- Extract value by key
SELECT ('{"a": 1}'::jsonb)['a'];
@@ -628,6 +643,9 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- needs to be of jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if jsonb_field is {"a": 1}
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Select records using where clause with subscripting. Since the result of
-- subscripting is jsonb and we basically want to compare two jsonb objects, we
-- need to put the value in double quotes to be able to convert it to jsonb.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--2v7pubbjx3p2om6u--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v45 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 22 ++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 9af015d222..924762e128 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -612,8 +612,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
<literal>path</literal> argument in <literal>jsonb_set</literal> function,
e.g. in case of arrays it is a 0-based operation or that negative integers
that appear in <literal>path</literal> count from the end of JSON arrays.
- The result of subscripting expressions is always jsonb data type. An
- example of subscripting syntax:
+ The result of subscripting expressions is always jsonb data type.
+
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Every
+ affected value must conform to the path defined by the subscript(s). If the
+ path contradicts structure of modified <type>jsonb</type> for any individual
+ value (e.g. path <literal>val['a']['b']['c']</literal> assumes keys
+ <literal>'a'</literal> and <literal>'b'</literal> have object values
+ assigned to them, but if <literal>val['a']</literal> or
+ <literal>val['b']</literal> is null, a string, or a number, then the path
+ contradicts with the existing structure), an error is raised even if other
+ values do conform.
+ </para>
+ <para>
+
+ An example of subscripting syntax:
<programlisting>
-- Extract value by key
SELECT ('{"a": 1}'::jsonb)['a'];
@@ -628,6 +643,9 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- needs to be of jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if jsonb_field is {"a": 1}
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Select records using where clause with subscripting. Since the result of
-- subscripting is jsonb and we basically want to compare two jsonb objects, we
-- need to put the value in double quotes to be able to convert it to jsonb.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--2v7pubbjx3p2om6u--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v44 3/3] Replace assuming a composite object on a scalar
@ 2021-01-07 08:04 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-07 08:04 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
3 files changed, 31 insertions(+)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b7c268b53f..0df808c36d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--i5uwshohlpbxyckd--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v48 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 27 +++++++++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 +++++++++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++++++
4 files changed, 90 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--m4t3wpskod5t4yye--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v46 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 50 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..1d33457788 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--mygb4tp4jvuwryfl--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v49 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 39 +++++++++++++++++++++++------
src/backend/utils/adt/jsonfuncs.c | 27 ++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 ++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++
4 files changed, 102 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..e16dd6973d 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,24 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Subscript
+ paths must be traversible for all affected values insofar as they exist. For
+ instance, the path <literal>val['a']['b']['c']</literal> can be traversed all
+ the way to <literal>c</literal> if every <literal>val</literal>,
+ <literal>val['a']</literal>, and <literal>val['a']['b']</literal> is an
+ object. If any <literal>val['a']</literal> or <literal>val['a']['b']</literal>
+ is not defined, it will be created as an empty object and filled as
+ necessary. However, if any <literal>val</literal> itself or one of the
+ intermediary values is defined as a non-object such as a string, number, or
+ <literal>jsonb</literal> <literal>null</literal>, traversal cannot proceed so
+ an error is raised and the transaction aborted.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +647,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
@@ -639,8 +659,9 @@ SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';
<type>jsonb</type> assignment via subscripting handles a few edge cases
differently from <literal>jsonb_set</literal>. When a source <type>jsonb</type>
- is <literal>NULL</literal>, assignment via subscripting will proceed as if
- it was an empty JSON object:
+ value is <literal>NULL</literal>, assignment via subscripting will proceed
+ as if it was an empty JSON value of the type (object or array) implied by the
+ subscript key:
<programlisting>
-- Where jsonb_field was NULL, it is now {"a": 1}
@@ -661,17 +682,19 @@ UPDATE table_name SET jsonb_field[2] = '2';
</programlisting>
A <type>jsonb</type> value will accept assignments to nonexistent subscript
- paths as long as the last existing path key is an object or an array. Since
- the final subscript is not traversed, it may be an object key. Nested arrays
- will be created and <literal>NULL</literal>-padded according to the path until
- the value can be placed appropriately.
+ paths as long as the last existing element to be traversed is an object or
+ array, as implied by the corresponding subscript (the element indicated by
+ the last subscript in the path is not traversed and may be anything). Nested
+ array and object structures will be created, and in the former case
+ <literal>null</literal>-padded, as specified by the subscript path until the
+ assigned value can be placed.
<programlisting>
-- Where jsonb_field was {}, it is now {'a': [{'b': 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
--- Where jsonb_field was [], it is now [{'a': 1}]
-UPDATE table_name SET jsonb_field[0]['a'] = '1';
+-- Where jsonb_field was [], it is now [null, {'a': 1}]
+UPDATE table_name SET jsonb_field[1]['a'] = '1';
</programlisting>
</para>
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--3x42k255okwqcjpg--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v46 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 50 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..1d33457788 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--mygb4tp4jvuwryfl--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v48 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 27 +++++++++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 +++++++++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++++++
4 files changed, 90 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--m4t3wpskod5t4yye--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v46 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 50 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..1d33457788 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--mygb4tp4jvuwryfl--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v48 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 27 +++++++++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 +++++++++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++++++
4 files changed, 90 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--m4t3wpskod5t4yye--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v48 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 27 +++++++++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 +++++++++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++++++
4 files changed, 90 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--m4t3wpskod5t4yye--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v46 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 50 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..1d33457788 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--mygb4tp4jvuwryfl--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v46 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 50 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..1d33457788 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--mygb4tp4jvuwryfl--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v46 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 50 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..1d33457788 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--mygb4tp4jvuwryfl--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v49 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 39 +++++++++++++++++++++++------
src/backend/utils/adt/jsonfuncs.c | 27 ++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 ++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++
4 files changed, 102 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..e16dd6973d 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,24 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Subscript
+ paths must be traversible for all affected values insofar as they exist. For
+ instance, the path <literal>val['a']['b']['c']</literal> can be traversed all
+ the way to <literal>c</literal> if every <literal>val</literal>,
+ <literal>val['a']</literal>, and <literal>val['a']['b']</literal> is an
+ object. If any <literal>val['a']</literal> or <literal>val['a']['b']</literal>
+ is not defined, it will be created as an empty object and filled as
+ necessary. However, if any <literal>val</literal> itself or one of the
+ intermediary values is defined as a non-object such as a string, number, or
+ <literal>jsonb</literal> <literal>null</literal>, traversal cannot proceed so
+ an error is raised and the transaction aborted.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +647,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
@@ -639,8 +659,9 @@ SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';
<type>jsonb</type> assignment via subscripting handles a few edge cases
differently from <literal>jsonb_set</literal>. When a source <type>jsonb</type>
- is <literal>NULL</literal>, assignment via subscripting will proceed as if
- it was an empty JSON object:
+ value is <literal>NULL</literal>, assignment via subscripting will proceed
+ as if it was an empty JSON value of the type (object or array) implied by the
+ subscript key:
<programlisting>
-- Where jsonb_field was NULL, it is now {"a": 1}
@@ -661,17 +682,19 @@ UPDATE table_name SET jsonb_field[2] = '2';
</programlisting>
A <type>jsonb</type> value will accept assignments to nonexistent subscript
- paths as long as the last existing path key is an object or an array. Since
- the final subscript is not traversed, it may be an object key. Nested arrays
- will be created and <literal>NULL</literal>-padded according to the path until
- the value can be placed appropriately.
+ paths as long as the last existing element to be traversed is an object or
+ array, as implied by the corresponding subscript (the element indicated by
+ the last subscript in the path is not traversed and may be anything). Nested
+ array and object structures will be created, and in the former case
+ <literal>null</literal>-padded, as specified by the subscript path until the
+ assigned value can be placed.
<programlisting>
-- Where jsonb_field was {}, it is now {'a': [{'b': 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
--- Where jsonb_field was [], it is now [{'a': 1}]
-UPDATE table_name SET jsonb_field[0]['a'] = '1';
+-- Where jsonb_field was [], it is now [null, {'a': 1}]
+UPDATE table_name SET jsonb_field[1]['a'] = '1';
</programlisting>
</para>
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--3x42k255okwqcjpg--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v48 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 27 +++++++++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 +++++++++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++++++
4 files changed, 90 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--m4t3wpskod5t4yye--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v49 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 39 +++++++++++++++++++++++------
src/backend/utils/adt/jsonfuncs.c | 27 ++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 ++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++
4 files changed, 102 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..e16dd6973d 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,24 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Subscript
+ paths must be traversible for all affected values insofar as they exist. For
+ instance, the path <literal>val['a']['b']['c']</literal> can be traversed all
+ the way to <literal>c</literal> if every <literal>val</literal>,
+ <literal>val['a']</literal>, and <literal>val['a']['b']</literal> is an
+ object. If any <literal>val['a']</literal> or <literal>val['a']['b']</literal>
+ is not defined, it will be created as an empty object and filled as
+ necessary. However, if any <literal>val</literal> itself or one of the
+ intermediary values is defined as a non-object such as a string, number, or
+ <literal>jsonb</literal> <literal>null</literal>, traversal cannot proceed so
+ an error is raised and the transaction aborted.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +647,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
@@ -639,8 +659,9 @@ SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';
<type>jsonb</type> assignment via subscripting handles a few edge cases
differently from <literal>jsonb_set</literal>. When a source <type>jsonb</type>
- is <literal>NULL</literal>, assignment via subscripting will proceed as if
- it was an empty JSON object:
+ value is <literal>NULL</literal>, assignment via subscripting will proceed
+ as if it was an empty JSON value of the type (object or array) implied by the
+ subscript key:
<programlisting>
-- Where jsonb_field was NULL, it is now {"a": 1}
@@ -661,17 +682,19 @@ UPDATE table_name SET jsonb_field[2] = '2';
</programlisting>
A <type>jsonb</type> value will accept assignments to nonexistent subscript
- paths as long as the last existing path key is an object or an array. Since
- the final subscript is not traversed, it may be an object key. Nested arrays
- will be created and <literal>NULL</literal>-padded according to the path until
- the value can be placed appropriately.
+ paths as long as the last existing element to be traversed is an object or
+ array, as implied by the corresponding subscript (the element indicated by
+ the last subscript in the path is not traversed and may be anything). Nested
+ array and object structures will be created, and in the former case
+ <literal>null</literal>-padded, as specified by the subscript path until the
+ assigned value can be placed.
<programlisting>
-- Where jsonb_field was {}, it is now {'a': [{'b': 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
--- Where jsonb_field was [], it is now [{'a': 1}]
-UPDATE table_name SET jsonb_field[0]['a'] = '1';
+-- Where jsonb_field was [], it is now [null, {'a': 1}]
+UPDATE table_name SET jsonb_field[1]['a'] = '1';
</programlisting>
</para>
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--3x42k255okwqcjpg--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v49 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 39 +++++++++++++++++++++++------
src/backend/utils/adt/jsonfuncs.c | 27 ++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 ++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++
4 files changed, 102 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..e16dd6973d 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,24 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Subscript
+ paths must be traversible for all affected values insofar as they exist. For
+ instance, the path <literal>val['a']['b']['c']</literal> can be traversed all
+ the way to <literal>c</literal> if every <literal>val</literal>,
+ <literal>val['a']</literal>, and <literal>val['a']['b']</literal> is an
+ object. If any <literal>val['a']</literal> or <literal>val['a']['b']</literal>
+ is not defined, it will be created as an empty object and filled as
+ necessary. However, if any <literal>val</literal> itself or one of the
+ intermediary values is defined as a non-object such as a string, number, or
+ <literal>jsonb</literal> <literal>null</literal>, traversal cannot proceed so
+ an error is raised and the transaction aborted.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +647,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
@@ -639,8 +659,9 @@ SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';
<type>jsonb</type> assignment via subscripting handles a few edge cases
differently from <literal>jsonb_set</literal>. When a source <type>jsonb</type>
- is <literal>NULL</literal>, assignment via subscripting will proceed as if
- it was an empty JSON object:
+ value is <literal>NULL</literal>, assignment via subscripting will proceed
+ as if it was an empty JSON value of the type (object or array) implied by the
+ subscript key:
<programlisting>
-- Where jsonb_field was NULL, it is now {"a": 1}
@@ -661,17 +682,19 @@ UPDATE table_name SET jsonb_field[2] = '2';
</programlisting>
A <type>jsonb</type> value will accept assignments to nonexistent subscript
- paths as long as the last existing path key is an object or an array. Since
- the final subscript is not traversed, it may be an object key. Nested arrays
- will be created and <literal>NULL</literal>-padded according to the path until
- the value can be placed appropriately.
+ paths as long as the last existing element to be traversed is an object or
+ array, as implied by the corresponding subscript (the element indicated by
+ the last subscript in the path is not traversed and may be anything). Nested
+ array and object structures will be created, and in the former case
+ <literal>null</literal>-padded, as specified by the subscript path until the
+ assigned value can be placed.
<programlisting>
-- Where jsonb_field was {}, it is now {'a': [{'b': 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
--- Where jsonb_field was [], it is now [{'a': 1}]
-UPDATE table_name SET jsonb_field[0]['a'] = '1';
+-- Where jsonb_field was [], it is now [null, {'a': 1}]
+UPDATE table_name SET jsonb_field[1]['a'] = '1';
</programlisting>
</para>
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--3x42k255okwqcjpg--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v46 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 50 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..1d33457788 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--mygb4tp4jvuwryfl--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v49 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 39 +++++++++++++++++++++++------
src/backend/utils/adt/jsonfuncs.c | 27 ++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 ++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++
4 files changed, 102 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..e16dd6973d 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,24 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Subscript
+ paths must be traversible for all affected values insofar as they exist. For
+ instance, the path <literal>val['a']['b']['c']</literal> can be traversed all
+ the way to <literal>c</literal> if every <literal>val</literal>,
+ <literal>val['a']</literal>, and <literal>val['a']['b']</literal> is an
+ object. If any <literal>val['a']</literal> or <literal>val['a']['b']</literal>
+ is not defined, it will be created as an empty object and filled as
+ necessary. However, if any <literal>val</literal> itself or one of the
+ intermediary values is defined as a non-object such as a string, number, or
+ <literal>jsonb</literal> <literal>null</literal>, traversal cannot proceed so
+ an error is raised and the transaction aborted.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +647,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
@@ -639,8 +659,9 @@ SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';
<type>jsonb</type> assignment via subscripting handles a few edge cases
differently from <literal>jsonb_set</literal>. When a source <type>jsonb</type>
- is <literal>NULL</literal>, assignment via subscripting will proceed as if
- it was an empty JSON object:
+ value is <literal>NULL</literal>, assignment via subscripting will proceed
+ as if it was an empty JSON value of the type (object or array) implied by the
+ subscript key:
<programlisting>
-- Where jsonb_field was NULL, it is now {"a": 1}
@@ -661,17 +682,19 @@ UPDATE table_name SET jsonb_field[2] = '2';
</programlisting>
A <type>jsonb</type> value will accept assignments to nonexistent subscript
- paths as long as the last existing path key is an object or an array. Since
- the final subscript is not traversed, it may be an object key. Nested arrays
- will be created and <literal>NULL</literal>-padded according to the path until
- the value can be placed appropriately.
+ paths as long as the last existing element to be traversed is an object or
+ array, as implied by the corresponding subscript (the element indicated by
+ the last subscript in the path is not traversed and may be anything). Nested
+ array and object structures will be created, and in the former case
+ <literal>null</literal>-padded, as specified by the subscript path until the
+ assigned value can be placed.
<programlisting>
-- Where jsonb_field was {}, it is now {'a': [{'b': 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
--- Where jsonb_field was [], it is now [{'a': 1}]
-UPDATE table_name SET jsonb_field[0]['a'] = '1';
+-- Where jsonb_field was [], it is now [null, {'a': 1}]
+UPDATE table_name SET jsonb_field[1]['a'] = '1';
</programlisting>
</para>
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--3x42k255okwqcjpg--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v48 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 27 +++++++++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 +++++++++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++++++
4 files changed, 90 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--m4t3wpskod5t4yye--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v48 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 27 +++++++++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 +++++++++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++++++
4 files changed, 90 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--m4t3wpskod5t4yye--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v48 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 27 +++++++++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 +++++++++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++++++
4 files changed, 90 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--m4t3wpskod5t4yye--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v48 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 27 +++++++++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 +++++++++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++++++
4 files changed, 90 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--m4t3wpskod5t4yye--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v46 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 50 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..1d33457788 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--mygb4tp4jvuwryfl--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v48 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 27 +++++++++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 +++++++++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++++++
4 files changed, 90 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--m4t3wpskod5t4yye--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v49 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 39 +++++++++++++++++++++++------
src/backend/utils/adt/jsonfuncs.c | 27 ++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 ++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++
4 files changed, 102 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..e16dd6973d 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,24 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Subscript
+ paths must be traversible for all affected values insofar as they exist. For
+ instance, the path <literal>val['a']['b']['c']</literal> can be traversed all
+ the way to <literal>c</literal> if every <literal>val</literal>,
+ <literal>val['a']</literal>, and <literal>val['a']['b']</literal> is an
+ object. If any <literal>val['a']</literal> or <literal>val['a']['b']</literal>
+ is not defined, it will be created as an empty object and filled as
+ necessary. However, if any <literal>val</literal> itself or one of the
+ intermediary values is defined as a non-object such as a string, number, or
+ <literal>jsonb</literal> <literal>null</literal>, traversal cannot proceed so
+ an error is raised and the transaction aborted.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +647,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
@@ -639,8 +659,9 @@ SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';
<type>jsonb</type> assignment via subscripting handles a few edge cases
differently from <literal>jsonb_set</literal>. When a source <type>jsonb</type>
- is <literal>NULL</literal>, assignment via subscripting will proceed as if
- it was an empty JSON object:
+ value is <literal>NULL</literal>, assignment via subscripting will proceed
+ as if it was an empty JSON value of the type (object or array) implied by the
+ subscript key:
<programlisting>
-- Where jsonb_field was NULL, it is now {"a": 1}
@@ -661,17 +682,19 @@ UPDATE table_name SET jsonb_field[2] = '2';
</programlisting>
A <type>jsonb</type> value will accept assignments to nonexistent subscript
- paths as long as the last existing path key is an object or an array. Since
- the final subscript is not traversed, it may be an object key. Nested arrays
- will be created and <literal>NULL</literal>-padded according to the path until
- the value can be placed appropriately.
+ paths as long as the last existing element to be traversed is an object or
+ array, as implied by the corresponding subscript (the element indicated by
+ the last subscript in the path is not traversed and may be anything). Nested
+ array and object structures will be created, and in the former case
+ <literal>null</literal>-padded, as specified by the subscript path until the
+ assigned value can be placed.
<programlisting>
-- Where jsonb_field was {}, it is now {'a': [{'b': 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
--- Where jsonb_field was [], it is now [{'a': 1}]
-UPDATE table_name SET jsonb_field[0]['a'] = '1';
+-- Where jsonb_field was [], it is now [null, {'a': 1}]
+UPDATE table_name SET jsonb_field[1]['a'] = '1';
</programlisting>
</para>
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--3x42k255okwqcjpg--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v46 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 50 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..1d33457788 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--mygb4tp4jvuwryfl--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v48 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 27 +++++++++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 +++++++++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++++++
4 files changed, 90 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--m4t3wpskod5t4yye--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v48 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 27 +++++++++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 +++++++++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++++++
4 files changed, 90 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--m4t3wpskod5t4yye--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v48 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 27 +++++++++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 +++++++++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++++++
4 files changed, 90 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--m4t3wpskod5t4yye--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v46 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 50 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..1d33457788 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--mygb4tp4jvuwryfl--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v49 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 39 +++++++++++++++++++++++------
src/backend/utils/adt/jsonfuncs.c | 27 ++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 ++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++
4 files changed, 102 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..e16dd6973d 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,24 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Subscript
+ paths must be traversible for all affected values insofar as they exist. For
+ instance, the path <literal>val['a']['b']['c']</literal> can be traversed all
+ the way to <literal>c</literal> if every <literal>val</literal>,
+ <literal>val['a']</literal>, and <literal>val['a']['b']</literal> is an
+ object. If any <literal>val['a']</literal> or <literal>val['a']['b']</literal>
+ is not defined, it will be created as an empty object and filled as
+ necessary. However, if any <literal>val</literal> itself or one of the
+ intermediary values is defined as a non-object such as a string, number, or
+ <literal>jsonb</literal> <literal>null</literal>, traversal cannot proceed so
+ an error is raised and the transaction aborted.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +647,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
@@ -639,8 +659,9 @@ SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';
<type>jsonb</type> assignment via subscripting handles a few edge cases
differently from <literal>jsonb_set</literal>. When a source <type>jsonb</type>
- is <literal>NULL</literal>, assignment via subscripting will proceed as if
- it was an empty JSON object:
+ value is <literal>NULL</literal>, assignment via subscripting will proceed
+ as if it was an empty JSON value of the type (object or array) implied by the
+ subscript key:
<programlisting>
-- Where jsonb_field was NULL, it is now {"a": 1}
@@ -661,17 +682,19 @@ UPDATE table_name SET jsonb_field[2] = '2';
</programlisting>
A <type>jsonb</type> value will accept assignments to nonexistent subscript
- paths as long as the last existing path key is an object or an array. Since
- the final subscript is not traversed, it may be an object key. Nested arrays
- will be created and <literal>NULL</literal>-padded according to the path until
- the value can be placed appropriately.
+ paths as long as the last existing element to be traversed is an object or
+ array, as implied by the corresponding subscript (the element indicated by
+ the last subscript in the path is not traversed and may be anything). Nested
+ array and object structures will be created, and in the former case
+ <literal>null</literal>-padded, as specified by the subscript path until the
+ assigned value can be placed.
<programlisting>
-- Where jsonb_field was {}, it is now {'a': [{'b': 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
--- Where jsonb_field was [], it is now [{'a': 1}]
-UPDATE table_name SET jsonb_field[0]['a'] = '1';
+-- Where jsonb_field was [], it is now [null, {'a': 1}]
+UPDATE table_name SET jsonb_field[1]['a'] = '1';
</programlisting>
</para>
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--3x42k255okwqcjpg--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v48 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 27 +++++++++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 +++++++++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++++++
4 files changed, 90 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--m4t3wpskod5t4yye--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v48 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 27 +++++++++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 +++++++++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++++++
4 files changed, 90 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--m4t3wpskod5t4yye--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v49 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 39 +++++++++++++++++++++++------
src/backend/utils/adt/jsonfuncs.c | 27 ++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 ++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++
4 files changed, 102 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..e16dd6973d 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,24 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Subscript
+ paths must be traversible for all affected values insofar as they exist. For
+ instance, the path <literal>val['a']['b']['c']</literal> can be traversed all
+ the way to <literal>c</literal> if every <literal>val</literal>,
+ <literal>val['a']</literal>, and <literal>val['a']['b']</literal> is an
+ object. If any <literal>val['a']</literal> or <literal>val['a']['b']</literal>
+ is not defined, it will be created as an empty object and filled as
+ necessary. However, if any <literal>val</literal> itself or one of the
+ intermediary values is defined as a non-object such as a string, number, or
+ <literal>jsonb</literal> <literal>null</literal>, traversal cannot proceed so
+ an error is raised and the transaction aborted.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +647,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
@@ -639,8 +659,9 @@ SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';
<type>jsonb</type> assignment via subscripting handles a few edge cases
differently from <literal>jsonb_set</literal>. When a source <type>jsonb</type>
- is <literal>NULL</literal>, assignment via subscripting will proceed as if
- it was an empty JSON object:
+ value is <literal>NULL</literal>, assignment via subscripting will proceed
+ as if it was an empty JSON value of the type (object or array) implied by the
+ subscript key:
<programlisting>
-- Where jsonb_field was NULL, it is now {"a": 1}
@@ -661,17 +682,19 @@ UPDATE table_name SET jsonb_field[2] = '2';
</programlisting>
A <type>jsonb</type> value will accept assignments to nonexistent subscript
- paths as long as the last existing path key is an object or an array. Since
- the final subscript is not traversed, it may be an object key. Nested arrays
- will be created and <literal>NULL</literal>-padded according to the path until
- the value can be placed appropriately.
+ paths as long as the last existing element to be traversed is an object or
+ array, as implied by the corresponding subscript (the element indicated by
+ the last subscript in the path is not traversed and may be anything). Nested
+ array and object structures will be created, and in the former case
+ <literal>null</literal>-padded, as specified by the subscript path until the
+ assigned value can be placed.
<programlisting>
-- Where jsonb_field was {}, it is now {'a': [{'b': 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
--- Where jsonb_field was [], it is now [{'a': 1}]
-UPDATE table_name SET jsonb_field[0]['a'] = '1';
+-- Where jsonb_field was [], it is now [null, {'a': 1}]
+UPDATE table_name SET jsonb_field[1]['a'] = '1';
</programlisting>
</para>
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--3x42k255okwqcjpg--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v49 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 39 +++++++++++++++++++++++------
src/backend/utils/adt/jsonfuncs.c | 27 ++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 ++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++
4 files changed, 102 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..e16dd6973d 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,24 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Subscript
+ paths must be traversible for all affected values insofar as they exist. For
+ instance, the path <literal>val['a']['b']['c']</literal> can be traversed all
+ the way to <literal>c</literal> if every <literal>val</literal>,
+ <literal>val['a']</literal>, and <literal>val['a']['b']</literal> is an
+ object. If any <literal>val['a']</literal> or <literal>val['a']['b']</literal>
+ is not defined, it will be created as an empty object and filled as
+ necessary. However, if any <literal>val</literal> itself or one of the
+ intermediary values is defined as a non-object such as a string, number, or
+ <literal>jsonb</literal> <literal>null</literal>, traversal cannot proceed so
+ an error is raised and the transaction aborted.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +647,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
@@ -639,8 +659,9 @@ SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';
<type>jsonb</type> assignment via subscripting handles a few edge cases
differently from <literal>jsonb_set</literal>. When a source <type>jsonb</type>
- is <literal>NULL</literal>, assignment via subscripting will proceed as if
- it was an empty JSON object:
+ value is <literal>NULL</literal>, assignment via subscripting will proceed
+ as if it was an empty JSON value of the type (object or array) implied by the
+ subscript key:
<programlisting>
-- Where jsonb_field was NULL, it is now {"a": 1}
@@ -661,17 +682,19 @@ UPDATE table_name SET jsonb_field[2] = '2';
</programlisting>
A <type>jsonb</type> value will accept assignments to nonexistent subscript
- paths as long as the last existing path key is an object or an array. Since
- the final subscript is not traversed, it may be an object key. Nested arrays
- will be created and <literal>NULL</literal>-padded according to the path until
- the value can be placed appropriately.
+ paths as long as the last existing element to be traversed is an object or
+ array, as implied by the corresponding subscript (the element indicated by
+ the last subscript in the path is not traversed and may be anything). Nested
+ array and object structures will be created, and in the former case
+ <literal>null</literal>-padded, as specified by the subscript path until the
+ assigned value can be placed.
<programlisting>
-- Where jsonb_field was {}, it is now {'a': [{'b': 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
--- Where jsonb_field was [], it is now [{'a': 1}]
-UPDATE table_name SET jsonb_field[0]['a'] = '1';
+-- Where jsonb_field was [], it is now [null, {'a': 1}]
+UPDATE table_name SET jsonb_field[1]['a'] = '1';
</programlisting>
</para>
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--3x42k255okwqcjpg--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v49 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 39 +++++++++++++++++++++++------
src/backend/utils/adt/jsonfuncs.c | 27 ++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 ++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++
4 files changed, 102 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..e16dd6973d 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,24 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Subscript
+ paths must be traversible for all affected values insofar as they exist. For
+ instance, the path <literal>val['a']['b']['c']</literal> can be traversed all
+ the way to <literal>c</literal> if every <literal>val</literal>,
+ <literal>val['a']</literal>, and <literal>val['a']['b']</literal> is an
+ object. If any <literal>val['a']</literal> or <literal>val['a']['b']</literal>
+ is not defined, it will be created as an empty object and filled as
+ necessary. However, if any <literal>val</literal> itself or one of the
+ intermediary values is defined as a non-object such as a string, number, or
+ <literal>jsonb</literal> <literal>null</literal>, traversal cannot proceed so
+ an error is raised and the transaction aborted.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +647,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
@@ -639,8 +659,9 @@ SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';
<type>jsonb</type> assignment via subscripting handles a few edge cases
differently from <literal>jsonb_set</literal>. When a source <type>jsonb</type>
- is <literal>NULL</literal>, assignment via subscripting will proceed as if
- it was an empty JSON object:
+ value is <literal>NULL</literal>, assignment via subscripting will proceed
+ as if it was an empty JSON value of the type (object or array) implied by the
+ subscript key:
<programlisting>
-- Where jsonb_field was NULL, it is now {"a": 1}
@@ -661,17 +682,19 @@ UPDATE table_name SET jsonb_field[2] = '2';
</programlisting>
A <type>jsonb</type> value will accept assignments to nonexistent subscript
- paths as long as the last existing path key is an object or an array. Since
- the final subscript is not traversed, it may be an object key. Nested arrays
- will be created and <literal>NULL</literal>-padded according to the path until
- the value can be placed appropriately.
+ paths as long as the last existing element to be traversed is an object or
+ array, as implied by the corresponding subscript (the element indicated by
+ the last subscript in the path is not traversed and may be anything). Nested
+ array and object structures will be created, and in the former case
+ <literal>null</literal>-padded, as specified by the subscript path until the
+ assigned value can be placed.
<programlisting>
-- Where jsonb_field was {}, it is now {'a': [{'b': 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
--- Where jsonb_field was [], it is now [{'a': 1}]
-UPDATE table_name SET jsonb_field[0]['a'] = '1';
+-- Where jsonb_field was [], it is now [null, {'a': 1}]
+UPDATE table_name SET jsonb_field[1]['a'] = '1';
</programlisting>
</para>
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--3x42k255okwqcjpg--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v48 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 27 +++++++++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 +++++++++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++++++
4 files changed, 90 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--m4t3wpskod5t4yye--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v46 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 50 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..1d33457788 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--mygb4tp4jvuwryfl--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v46 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 50 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..1d33457788 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--mygb4tp4jvuwryfl--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v48 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 27 +++++++++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 +++++++++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++++++
4 files changed, 90 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--m4t3wpskod5t4yye--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v49 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 39 +++++++++++++++++++++++------
src/backend/utils/adt/jsonfuncs.c | 27 ++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 ++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++
4 files changed, 102 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..e16dd6973d 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,24 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Subscript
+ paths must be traversible for all affected values insofar as they exist. For
+ instance, the path <literal>val['a']['b']['c']</literal> can be traversed all
+ the way to <literal>c</literal> if every <literal>val</literal>,
+ <literal>val['a']</literal>, and <literal>val['a']['b']</literal> is an
+ object. If any <literal>val['a']</literal> or <literal>val['a']['b']</literal>
+ is not defined, it will be created as an empty object and filled as
+ necessary. However, if any <literal>val</literal> itself or one of the
+ intermediary values is defined as a non-object such as a string, number, or
+ <literal>jsonb</literal> <literal>null</literal>, traversal cannot proceed so
+ an error is raised and the transaction aborted.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +647,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
@@ -639,8 +659,9 @@ SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';
<type>jsonb</type> assignment via subscripting handles a few edge cases
differently from <literal>jsonb_set</literal>. When a source <type>jsonb</type>
- is <literal>NULL</literal>, assignment via subscripting will proceed as if
- it was an empty JSON object:
+ value is <literal>NULL</literal>, assignment via subscripting will proceed
+ as if it was an empty JSON value of the type (object or array) implied by the
+ subscript key:
<programlisting>
-- Where jsonb_field was NULL, it is now {"a": 1}
@@ -661,17 +682,19 @@ UPDATE table_name SET jsonb_field[2] = '2';
</programlisting>
A <type>jsonb</type> value will accept assignments to nonexistent subscript
- paths as long as the last existing path key is an object or an array. Since
- the final subscript is not traversed, it may be an object key. Nested arrays
- will be created and <literal>NULL</literal>-padded according to the path until
- the value can be placed appropriately.
+ paths as long as the last existing element to be traversed is an object or
+ array, as implied by the corresponding subscript (the element indicated by
+ the last subscript in the path is not traversed and may be anything). Nested
+ array and object structures will be created, and in the former case
+ <literal>null</literal>-padded, as specified by the subscript path until the
+ assigned value can be placed.
<programlisting>
-- Where jsonb_field was {}, it is now {'a': [{'b': 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
--- Where jsonb_field was [], it is now [{'a': 1}]
-UPDATE table_name SET jsonb_field[0]['a'] = '1';
+-- Where jsonb_field was [], it is now [null, {'a': 1}]
+UPDATE table_name SET jsonb_field[1]['a'] = '1';
</programlisting>
</para>
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--3x42k255okwqcjpg--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v46 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 50 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..1d33457788 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--mygb4tp4jvuwryfl--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v49 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 39 +++++++++++++++++++++++------
src/backend/utils/adt/jsonfuncs.c | 27 ++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 ++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++
4 files changed, 102 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..e16dd6973d 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,24 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Subscript
+ paths must be traversible for all affected values insofar as they exist. For
+ instance, the path <literal>val['a']['b']['c']</literal> can be traversed all
+ the way to <literal>c</literal> if every <literal>val</literal>,
+ <literal>val['a']</literal>, and <literal>val['a']['b']</literal> is an
+ object. If any <literal>val['a']</literal> or <literal>val['a']['b']</literal>
+ is not defined, it will be created as an empty object and filled as
+ necessary. However, if any <literal>val</literal> itself or one of the
+ intermediary values is defined as a non-object such as a string, number, or
+ <literal>jsonb</literal> <literal>null</literal>, traversal cannot proceed so
+ an error is raised and the transaction aborted.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +647,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
@@ -639,8 +659,9 @@ SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';
<type>jsonb</type> assignment via subscripting handles a few edge cases
differently from <literal>jsonb_set</literal>. When a source <type>jsonb</type>
- is <literal>NULL</literal>, assignment via subscripting will proceed as if
- it was an empty JSON object:
+ value is <literal>NULL</literal>, assignment via subscripting will proceed
+ as if it was an empty JSON value of the type (object or array) implied by the
+ subscript key:
<programlisting>
-- Where jsonb_field was NULL, it is now {"a": 1}
@@ -661,17 +682,19 @@ UPDATE table_name SET jsonb_field[2] = '2';
</programlisting>
A <type>jsonb</type> value will accept assignments to nonexistent subscript
- paths as long as the last existing path key is an object or an array. Since
- the final subscript is not traversed, it may be an object key. Nested arrays
- will be created and <literal>NULL</literal>-padded according to the path until
- the value can be placed appropriately.
+ paths as long as the last existing element to be traversed is an object or
+ array, as implied by the corresponding subscript (the element indicated by
+ the last subscript in the path is not traversed and may be anything). Nested
+ array and object structures will be created, and in the former case
+ <literal>null</literal>-padded, as specified by the subscript path until the
+ assigned value can be placed.
<programlisting>
-- Where jsonb_field was {}, it is now {'a': [{'b': 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
--- Where jsonb_field was [], it is now [{'a': 1}]
-UPDATE table_name SET jsonb_field[0]['a'] = '1';
+-- Where jsonb_field was [], it is now [null, {'a': 1}]
+UPDATE table_name SET jsonb_field[1]['a'] = '1';
</programlisting>
</para>
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--3x42k255okwqcjpg--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v46 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 50 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..1d33457788 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--mygb4tp4jvuwryfl--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v49 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 39 +++++++++++++++++++++++------
src/backend/utils/adt/jsonfuncs.c | 27 ++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 ++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++
4 files changed, 102 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..e16dd6973d 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,24 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Subscript
+ paths must be traversible for all affected values insofar as they exist. For
+ instance, the path <literal>val['a']['b']['c']</literal> can be traversed all
+ the way to <literal>c</literal> if every <literal>val</literal>,
+ <literal>val['a']</literal>, and <literal>val['a']['b']</literal> is an
+ object. If any <literal>val['a']</literal> or <literal>val['a']['b']</literal>
+ is not defined, it will be created as an empty object and filled as
+ necessary. However, if any <literal>val</literal> itself or one of the
+ intermediary values is defined as a non-object such as a string, number, or
+ <literal>jsonb</literal> <literal>null</literal>, traversal cannot proceed so
+ an error is raised and the transaction aborted.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +647,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
@@ -639,8 +659,9 @@ SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';
<type>jsonb</type> assignment via subscripting handles a few edge cases
differently from <literal>jsonb_set</literal>. When a source <type>jsonb</type>
- is <literal>NULL</literal>, assignment via subscripting will proceed as if
- it was an empty JSON object:
+ value is <literal>NULL</literal>, assignment via subscripting will proceed
+ as if it was an empty JSON value of the type (object or array) implied by the
+ subscript key:
<programlisting>
-- Where jsonb_field was NULL, it is now {"a": 1}
@@ -661,17 +682,19 @@ UPDATE table_name SET jsonb_field[2] = '2';
</programlisting>
A <type>jsonb</type> value will accept assignments to nonexistent subscript
- paths as long as the last existing path key is an object or an array. Since
- the final subscript is not traversed, it may be an object key. Nested arrays
- will be created and <literal>NULL</literal>-padded according to the path until
- the value can be placed appropriately.
+ paths as long as the last existing element to be traversed is an object or
+ array, as implied by the corresponding subscript (the element indicated by
+ the last subscript in the path is not traversed and may be anything). Nested
+ array and object structures will be created, and in the former case
+ <literal>null</literal>-padded, as specified by the subscript path until the
+ assigned value can be placed.
<programlisting>
-- Where jsonb_field was {}, it is now {'a': [{'b': 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
--- Where jsonb_field was [], it is now [{'a': 1}]
-UPDATE table_name SET jsonb_field[0]['a'] = '1';
+-- Where jsonb_field was [], it is now [null, {'a': 1}]
+UPDATE table_name SET jsonb_field[1]['a'] = '1';
</programlisting>
</para>
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--3x42k255okwqcjpg--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v49 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 39 +++++++++++++++++++++++------
src/backend/utils/adt/jsonfuncs.c | 27 ++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 ++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++
4 files changed, 102 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..e16dd6973d 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,24 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Subscript
+ paths must be traversible for all affected values insofar as they exist. For
+ instance, the path <literal>val['a']['b']['c']</literal> can be traversed all
+ the way to <literal>c</literal> if every <literal>val</literal>,
+ <literal>val['a']</literal>, and <literal>val['a']['b']</literal> is an
+ object. If any <literal>val['a']</literal> or <literal>val['a']['b']</literal>
+ is not defined, it will be created as an empty object and filled as
+ necessary. However, if any <literal>val</literal> itself or one of the
+ intermediary values is defined as a non-object such as a string, number, or
+ <literal>jsonb</literal> <literal>null</literal>, traversal cannot proceed so
+ an error is raised and the transaction aborted.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +647,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
@@ -639,8 +659,9 @@ SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';
<type>jsonb</type> assignment via subscripting handles a few edge cases
differently from <literal>jsonb_set</literal>. When a source <type>jsonb</type>
- is <literal>NULL</literal>, assignment via subscripting will proceed as if
- it was an empty JSON object:
+ value is <literal>NULL</literal>, assignment via subscripting will proceed
+ as if it was an empty JSON value of the type (object or array) implied by the
+ subscript key:
<programlisting>
-- Where jsonb_field was NULL, it is now {"a": 1}
@@ -661,17 +682,19 @@ UPDATE table_name SET jsonb_field[2] = '2';
</programlisting>
A <type>jsonb</type> value will accept assignments to nonexistent subscript
- paths as long as the last existing path key is an object or an array. Since
- the final subscript is not traversed, it may be an object key. Nested arrays
- will be created and <literal>NULL</literal>-padded according to the path until
- the value can be placed appropriately.
+ paths as long as the last existing element to be traversed is an object or
+ array, as implied by the corresponding subscript (the element indicated by
+ the last subscript in the path is not traversed and may be anything). Nested
+ array and object structures will be created, and in the former case
+ <literal>null</literal>-padded, as specified by the subscript path until the
+ assigned value can be placed.
<programlisting>
-- Where jsonb_field was {}, it is now {'a': [{'b': 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
--- Where jsonb_field was [], it is now [{'a': 1}]
-UPDATE table_name SET jsonb_field[0]['a'] = '1';
+-- Where jsonb_field was [], it is now [null, {'a': 1}]
+UPDATE table_name SET jsonb_field[1]['a'] = '1';
</programlisting>
</para>
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--3x42k255okwqcjpg--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v49 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 39 +++++++++++++++++++++++------
src/backend/utils/adt/jsonfuncs.c | 27 ++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 ++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++
4 files changed, 102 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..e16dd6973d 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,24 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Subscript
+ paths must be traversible for all affected values insofar as they exist. For
+ instance, the path <literal>val['a']['b']['c']</literal> can be traversed all
+ the way to <literal>c</literal> if every <literal>val</literal>,
+ <literal>val['a']</literal>, and <literal>val['a']['b']</literal> is an
+ object. If any <literal>val['a']</literal> or <literal>val['a']['b']</literal>
+ is not defined, it will be created as an empty object and filled as
+ necessary. However, if any <literal>val</literal> itself or one of the
+ intermediary values is defined as a non-object such as a string, number, or
+ <literal>jsonb</literal> <literal>null</literal>, traversal cannot proceed so
+ an error is raised and the transaction aborted.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +647,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
@@ -639,8 +659,9 @@ SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';
<type>jsonb</type> assignment via subscripting handles a few edge cases
differently from <literal>jsonb_set</literal>. When a source <type>jsonb</type>
- is <literal>NULL</literal>, assignment via subscripting will proceed as if
- it was an empty JSON object:
+ value is <literal>NULL</literal>, assignment via subscripting will proceed
+ as if it was an empty JSON value of the type (object or array) implied by the
+ subscript key:
<programlisting>
-- Where jsonb_field was NULL, it is now {"a": 1}
@@ -661,17 +682,19 @@ UPDATE table_name SET jsonb_field[2] = '2';
</programlisting>
A <type>jsonb</type> value will accept assignments to nonexistent subscript
- paths as long as the last existing path key is an object or an array. Since
- the final subscript is not traversed, it may be an object key. Nested arrays
- will be created and <literal>NULL</literal>-padded according to the path until
- the value can be placed appropriately.
+ paths as long as the last existing element to be traversed is an object or
+ array, as implied by the corresponding subscript (the element indicated by
+ the last subscript in the path is not traversed and may be anything). Nested
+ array and object structures will be created, and in the former case
+ <literal>null</literal>-padded, as specified by the subscript path until the
+ assigned value can be placed.
<programlisting>
-- Where jsonb_field was {}, it is now {'a': [{'b': 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
--- Where jsonb_field was [], it is now [{'a': 1}]
-UPDATE table_name SET jsonb_field[0]['a'] = '1';
+-- Where jsonb_field was [], it is now [null, {'a': 1}]
+UPDATE table_name SET jsonb_field[1]['a'] = '1';
</programlisting>
</para>
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--3x42k255okwqcjpg--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v46 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 50 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..1d33457788 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--mygb4tp4jvuwryfl--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v49 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 39 +++++++++++++++++++++++------
src/backend/utils/adt/jsonfuncs.c | 27 ++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 ++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++
4 files changed, 102 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..e16dd6973d 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,24 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Subscript
+ paths must be traversible for all affected values insofar as they exist. For
+ instance, the path <literal>val['a']['b']['c']</literal> can be traversed all
+ the way to <literal>c</literal> if every <literal>val</literal>,
+ <literal>val['a']</literal>, and <literal>val['a']['b']</literal> is an
+ object. If any <literal>val['a']</literal> or <literal>val['a']['b']</literal>
+ is not defined, it will be created as an empty object and filled as
+ necessary. However, if any <literal>val</literal> itself or one of the
+ intermediary values is defined as a non-object such as a string, number, or
+ <literal>jsonb</literal> <literal>null</literal>, traversal cannot proceed so
+ an error is raised and the transaction aborted.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +647,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
@@ -639,8 +659,9 @@ SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';
<type>jsonb</type> assignment via subscripting handles a few edge cases
differently from <literal>jsonb_set</literal>. When a source <type>jsonb</type>
- is <literal>NULL</literal>, assignment via subscripting will proceed as if
- it was an empty JSON object:
+ value is <literal>NULL</literal>, assignment via subscripting will proceed
+ as if it was an empty JSON value of the type (object or array) implied by the
+ subscript key:
<programlisting>
-- Where jsonb_field was NULL, it is now {"a": 1}
@@ -661,17 +682,19 @@ UPDATE table_name SET jsonb_field[2] = '2';
</programlisting>
A <type>jsonb</type> value will accept assignments to nonexistent subscript
- paths as long as the last existing path key is an object or an array. Since
- the final subscript is not traversed, it may be an object key. Nested arrays
- will be created and <literal>NULL</literal>-padded according to the path until
- the value can be placed appropriately.
+ paths as long as the last existing element to be traversed is an object or
+ array, as implied by the corresponding subscript (the element indicated by
+ the last subscript in the path is not traversed and may be anything). Nested
+ array and object structures will be created, and in the former case
+ <literal>null</literal>-padded, as specified by the subscript path until the
+ assigned value can be placed.
<programlisting>
-- Where jsonb_field was {}, it is now {'a': [{'b': 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
--- Where jsonb_field was [], it is now [{'a': 1}]
-UPDATE table_name SET jsonb_field[0]['a'] = '1';
+-- Where jsonb_field was [], it is now [null, {'a': 1}]
+UPDATE table_name SET jsonb_field[1]['a'] = '1';
</programlisting>
</para>
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--3x42k255okwqcjpg--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v46 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 50 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..1d33457788 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--mygb4tp4jvuwryfl--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v48 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 27 +++++++++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 +++++++++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++++++
4 files changed, 90 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--m4t3wpskod5t4yye--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v46 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 50 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..1d33457788 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--mygb4tp4jvuwryfl--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v49 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 39 +++++++++++++++++++++++------
src/backend/utils/adt/jsonfuncs.c | 27 ++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 ++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++
4 files changed, 102 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..e16dd6973d 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,24 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Subscript
+ paths must be traversible for all affected values insofar as they exist. For
+ instance, the path <literal>val['a']['b']['c']</literal> can be traversed all
+ the way to <literal>c</literal> if every <literal>val</literal>,
+ <literal>val['a']</literal>, and <literal>val['a']['b']</literal> is an
+ object. If any <literal>val['a']</literal> or <literal>val['a']['b']</literal>
+ is not defined, it will be created as an empty object and filled as
+ necessary. However, if any <literal>val</literal> itself or one of the
+ intermediary values is defined as a non-object such as a string, number, or
+ <literal>jsonb</literal> <literal>null</literal>, traversal cannot proceed so
+ an error is raised and the transaction aborted.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +647,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
@@ -639,8 +659,9 @@ SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';
<type>jsonb</type> assignment via subscripting handles a few edge cases
differently from <literal>jsonb_set</literal>. When a source <type>jsonb</type>
- is <literal>NULL</literal>, assignment via subscripting will proceed as if
- it was an empty JSON object:
+ value is <literal>NULL</literal>, assignment via subscripting will proceed
+ as if it was an empty JSON value of the type (object or array) implied by the
+ subscript key:
<programlisting>
-- Where jsonb_field was NULL, it is now {"a": 1}
@@ -661,17 +682,19 @@ UPDATE table_name SET jsonb_field[2] = '2';
</programlisting>
A <type>jsonb</type> value will accept assignments to nonexistent subscript
- paths as long as the last existing path key is an object or an array. Since
- the final subscript is not traversed, it may be an object key. Nested arrays
- will be created and <literal>NULL</literal>-padded according to the path until
- the value can be placed appropriately.
+ paths as long as the last existing element to be traversed is an object or
+ array, as implied by the corresponding subscript (the element indicated by
+ the last subscript in the path is not traversed and may be anything). Nested
+ array and object structures will be created, and in the former case
+ <literal>null</literal>-padded, as specified by the subscript path until the
+ assigned value can be placed.
<programlisting>
-- Where jsonb_field was {}, it is now {'a': [{'b': 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
--- Where jsonb_field was [], it is now [{'a': 1}]
-UPDATE table_name SET jsonb_field[0]['a'] = '1';
+-- Where jsonb_field was [], it is now [null, {'a': 1}]
+UPDATE table_name SET jsonb_field[1]['a'] = '1';
</programlisting>
</para>
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--3x42k255okwqcjpg--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v48 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 27 +++++++++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 +++++++++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++++++
4 files changed, 90 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--m4t3wpskod5t4yye--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v49 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 39 +++++++++++++++++++++++------
src/backend/utils/adt/jsonfuncs.c | 27 ++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 ++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++
4 files changed, 102 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..e16dd6973d 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,24 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Subscript
+ paths must be traversible for all affected values insofar as they exist. For
+ instance, the path <literal>val['a']['b']['c']</literal> can be traversed all
+ the way to <literal>c</literal> if every <literal>val</literal>,
+ <literal>val['a']</literal>, and <literal>val['a']['b']</literal> is an
+ object. If any <literal>val['a']</literal> or <literal>val['a']['b']</literal>
+ is not defined, it will be created as an empty object and filled as
+ necessary. However, if any <literal>val</literal> itself or one of the
+ intermediary values is defined as a non-object such as a string, number, or
+ <literal>jsonb</literal> <literal>null</literal>, traversal cannot proceed so
+ an error is raised and the transaction aborted.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +647,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
@@ -639,8 +659,9 @@ SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';
<type>jsonb</type> assignment via subscripting handles a few edge cases
differently from <literal>jsonb_set</literal>. When a source <type>jsonb</type>
- is <literal>NULL</literal>, assignment via subscripting will proceed as if
- it was an empty JSON object:
+ value is <literal>NULL</literal>, assignment via subscripting will proceed
+ as if it was an empty JSON value of the type (object or array) implied by the
+ subscript key:
<programlisting>
-- Where jsonb_field was NULL, it is now {"a": 1}
@@ -661,17 +682,19 @@ UPDATE table_name SET jsonb_field[2] = '2';
</programlisting>
A <type>jsonb</type> value will accept assignments to nonexistent subscript
- paths as long as the last existing path key is an object or an array. Since
- the final subscript is not traversed, it may be an object key. Nested arrays
- will be created and <literal>NULL</literal>-padded according to the path until
- the value can be placed appropriately.
+ paths as long as the last existing element to be traversed is an object or
+ array, as implied by the corresponding subscript (the element indicated by
+ the last subscript in the path is not traversed and may be anything). Nested
+ array and object structures will be created, and in the former case
+ <literal>null</literal>-padded, as specified by the subscript path until the
+ assigned value can be placed.
<programlisting>
-- Where jsonb_field was {}, it is now {'a': [{'b': 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
--- Where jsonb_field was [], it is now [{'a': 1}]
-UPDATE table_name SET jsonb_field[0]['a'] = '1';
+-- Where jsonb_field was [], it is now [null, {'a': 1}]
+UPDATE table_name SET jsonb_field[1]['a'] = '1';
</programlisting>
</para>
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--3x42k255okwqcjpg--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v48 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 27 +++++++++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 +++++++++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++++++
4 files changed, 90 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--m4t3wpskod5t4yye--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v46 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 50 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..1d33457788 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--mygb4tp4jvuwryfl--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v46 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 50 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..1d33457788 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--mygb4tp4jvuwryfl--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v49 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 39 +++++++++++++++++++++++------
src/backend/utils/adt/jsonfuncs.c | 27 ++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 ++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++
4 files changed, 102 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..e16dd6973d 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,24 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Subscript
+ paths must be traversible for all affected values insofar as they exist. For
+ instance, the path <literal>val['a']['b']['c']</literal> can be traversed all
+ the way to <literal>c</literal> if every <literal>val</literal>,
+ <literal>val['a']</literal>, and <literal>val['a']['b']</literal> is an
+ object. If any <literal>val['a']</literal> or <literal>val['a']['b']</literal>
+ is not defined, it will be created as an empty object and filled as
+ necessary. However, if any <literal>val</literal> itself or one of the
+ intermediary values is defined as a non-object such as a string, number, or
+ <literal>jsonb</literal> <literal>null</literal>, traversal cannot proceed so
+ an error is raised and the transaction aborted.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +647,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
@@ -639,8 +659,9 @@ SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';
<type>jsonb</type> assignment via subscripting handles a few edge cases
differently from <literal>jsonb_set</literal>. When a source <type>jsonb</type>
- is <literal>NULL</literal>, assignment via subscripting will proceed as if
- it was an empty JSON object:
+ value is <literal>NULL</literal>, assignment via subscripting will proceed
+ as if it was an empty JSON value of the type (object or array) implied by the
+ subscript key:
<programlisting>
-- Where jsonb_field was NULL, it is now {"a": 1}
@@ -661,17 +682,19 @@ UPDATE table_name SET jsonb_field[2] = '2';
</programlisting>
A <type>jsonb</type> value will accept assignments to nonexistent subscript
- paths as long as the last existing path key is an object or an array. Since
- the final subscript is not traversed, it may be an object key. Nested arrays
- will be created and <literal>NULL</literal>-padded according to the path until
- the value can be placed appropriately.
+ paths as long as the last existing element to be traversed is an object or
+ array, as implied by the corresponding subscript (the element indicated by
+ the last subscript in the path is not traversed and may be anything). Nested
+ array and object structures will be created, and in the former case
+ <literal>null</literal>-padded, as specified by the subscript path until the
+ assigned value can be placed.
<programlisting>
-- Where jsonb_field was {}, it is now {'a': [{'b': 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
--- Where jsonb_field was [], it is now [{'a': 1}]
-UPDATE table_name SET jsonb_field[0]['a'] = '1';
+-- Where jsonb_field was [], it is now [null, {'a': 1}]
+UPDATE table_name SET jsonb_field[1]['a'] = '1';
</programlisting>
</para>
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--3x42k255okwqcjpg--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v46 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 50 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..1d33457788 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--mygb4tp4jvuwryfl--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v48 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 27 +++++++++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 +++++++++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++++++
4 files changed, 90 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--m4t3wpskod5t4yye--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v49 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 39 +++++++++++++++++++++++------
src/backend/utils/adt/jsonfuncs.c | 27 ++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 ++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++
4 files changed, 102 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..e16dd6973d 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,24 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Subscript
+ paths must be traversible for all affected values insofar as they exist. For
+ instance, the path <literal>val['a']['b']['c']</literal> can be traversed all
+ the way to <literal>c</literal> if every <literal>val</literal>,
+ <literal>val['a']</literal>, and <literal>val['a']['b']</literal> is an
+ object. If any <literal>val['a']</literal> or <literal>val['a']['b']</literal>
+ is not defined, it will be created as an empty object and filled as
+ necessary. However, if any <literal>val</literal> itself or one of the
+ intermediary values is defined as a non-object such as a string, number, or
+ <literal>jsonb</literal> <literal>null</literal>, traversal cannot proceed so
+ an error is raised and the transaction aborted.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +647,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
@@ -639,8 +659,9 @@ SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';
<type>jsonb</type> assignment via subscripting handles a few edge cases
differently from <literal>jsonb_set</literal>. When a source <type>jsonb</type>
- is <literal>NULL</literal>, assignment via subscripting will proceed as if
- it was an empty JSON object:
+ value is <literal>NULL</literal>, assignment via subscripting will proceed
+ as if it was an empty JSON value of the type (object or array) implied by the
+ subscript key:
<programlisting>
-- Where jsonb_field was NULL, it is now {"a": 1}
@@ -661,17 +682,19 @@ UPDATE table_name SET jsonb_field[2] = '2';
</programlisting>
A <type>jsonb</type> value will accept assignments to nonexistent subscript
- paths as long as the last existing path key is an object or an array. Since
- the final subscript is not traversed, it may be an object key. Nested arrays
- will be created and <literal>NULL</literal>-padded according to the path until
- the value can be placed appropriately.
+ paths as long as the last existing element to be traversed is an object or
+ array, as implied by the corresponding subscript (the element indicated by
+ the last subscript in the path is not traversed and may be anything). Nested
+ array and object structures will be created, and in the former case
+ <literal>null</literal>-padded, as specified by the subscript path until the
+ assigned value can be placed.
<programlisting>
-- Where jsonb_field was {}, it is now {'a': [{'b': 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
--- Where jsonb_field was [], it is now [{'a': 1}]
-UPDATE table_name SET jsonb_field[0]['a'] = '1';
+-- Where jsonb_field was [], it is now [null, {'a': 1}]
+UPDATE table_name SET jsonb_field[1]['a'] = '1';
</programlisting>
</para>
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--3x42k255okwqcjpg--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v46 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 19 +++++++++++++++++++
src/backend/utils/adt/jsonfuncs.c | 11 +++++++++++
src/test/regress/expected/jsonb.out | 12 ++++++++++++
src/test/regress/sql/jsonb.sql | 8 ++++++++
4 files changed, 50 insertions(+)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..deeb9e66e0 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,23 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Object
+ values being traversed must exist as specified by the subscript path. For
+ instance, the path <literal>val['a']['b']['c']</literal> assumes that
+ <literal>val</literal>, <literal>val['a']</literal>, and <literal>val['a']['b']</literal>
+ are all objects in every record being updated (<literal>val['a']['b']</literal>
+ may or may not contain a field named <literal>c</literal>, as long as it's an
+ object). If any individual <literal>val</literal>, <literal>val['a']</literal>,
+ or <literal>val['a']['b']</literal> is a non-object such as a string, a number,
+ or <literal>NULL</literal>, an error is raised even if other values do conform.
+ Array values are not subject to this restriction, as detailed below.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +646,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..ebc0b06f5b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4943,6 +4943,17 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..1d33457788 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,18 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's a scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..c62a2f9aec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,14 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's a scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--mygb4tp4jvuwryfl--
^ permalink raw reply [nested|flat] 108+ messages in thread
* [PATCH v49 3/3] Replace assuming a composite object on a scalar
@ 2021-01-20 15:53 Dmitrii Dolgov <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Dmitrii Dolgov @ 2021-01-20 15:53 UTC (permalink / raw)
For jsonb subscripting assignment it could happen that the provided path
assumes an object or an array at some level, but the source jsonb has a
scalar value there. Originally the update operation will be skipped and
no message will be shown that nothing happened. Return an error to
indicate such situations.
---
doc/src/sgml/json.sgml | 39 +++++++++++++++++++++++------
src/backend/utils/adt/jsonfuncs.c | 27 ++++++++++++++++++++
src/test/regress/expected/jsonb.out | 27 ++++++++++++++++++++
src/test/regress/sql/jsonb.sql | 17 +++++++++++++
4 files changed, 102 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 07bd19f974..e16dd6973d 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -614,8 +614,24 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
The result of a subscripting expression is always of the jsonb data type.
</para>
+ <para>
+ <command>UPDATE</command> statements may use subscripting in the
+ <literal>SET</literal> clause to modify <type>jsonb</type> values. Subscript
+ paths must be traversible for all affected values insofar as they exist. For
+ instance, the path <literal>val['a']['b']['c']</literal> can be traversed all
+ the way to <literal>c</literal> if every <literal>val</literal>,
+ <literal>val['a']</literal>, and <literal>val['a']['b']</literal> is an
+ object. If any <literal>val['a']</literal> or <literal>val['a']['b']</literal>
+ is not defined, it will be created as an empty object and filled as
+ necessary. However, if any <literal>val</literal> itself or one of the
+ intermediary values is defined as a non-object such as a string, number, or
+ <literal>jsonb</literal> <literal>null</literal>, traversal cannot proceed so
+ an error is raised and the transaction aborted.
+ </para>
+
<para>
An example of subscripting syntax:
+
<programlisting>
-- Extract object value by key
@@ -631,6 +647,10 @@ SELECT ('[1, "2", null]'::jsonb)[1];
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
+-- This will raise an error if any record's jsonb_field['a']['b'] is something
+-- other than an object. For example, the value {"a": 1} has no 'b' key.
+UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
+
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
@@ -639,8 +659,9 @@ SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';
<type>jsonb</type> assignment via subscripting handles a few edge cases
differently from <literal>jsonb_set</literal>. When a source <type>jsonb</type>
- is <literal>NULL</literal>, assignment via subscripting will proceed as if
- it was an empty JSON object:
+ value is <literal>NULL</literal>, assignment via subscripting will proceed
+ as if it was an empty JSON value of the type (object or array) implied by the
+ subscript key:
<programlisting>
-- Where jsonb_field was NULL, it is now {"a": 1}
@@ -661,17 +682,19 @@ UPDATE table_name SET jsonb_field[2] = '2';
</programlisting>
A <type>jsonb</type> value will accept assignments to nonexistent subscript
- paths as long as the last existing path key is an object or an array. Since
- the final subscript is not traversed, it may be an object key. Nested arrays
- will be created and <literal>NULL</literal>-padded according to the path until
- the value can be placed appropriately.
+ paths as long as the last existing element to be traversed is an object or
+ array, as implied by the corresponding subscript (the element indicated by
+ the last subscript in the path is not traversed and may be anything). Nested
+ array and object structures will be created, and in the former case
+ <literal>null</literal>-padded, as specified by the subscript path until the
+ assigned value can be placed.
<programlisting>
-- Where jsonb_field was {}, it is now {'a': [{'b': 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
--- Where jsonb_field was [], it is now [{'a': 1}]
-UPDATE table_name SET jsonb_field[0]['a'] = '1';
+-- Where jsonb_field was [], it is now [null, {'a': 1}]
+UPDATE table_name SET jsonb_field[1]['a'] = '1';
</programlisting>
</para>
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index f14f6c3191..9138b7950e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -4926,6 +4926,20 @@ setPath(JsonbIterator **it, Datum *path_elems,
switch (r)
{
case WJB_BEGIN_ARRAY:
+ /*
+ * If instructed complain about attempts to replace whithin a raw
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not raw scalar.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) &&
+ v.val.array.rawScalar)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
(void) pushJsonbValue(st, r, NULL);
setPathArray(it, path_elems, path_nulls, path_len, st, level,
newval, v.val.array.nElems, op_type);
@@ -4943,6 +4957,19 @@ setPath(JsonbIterator **it, Datum *path_elems,
break;
case WJB_ELEM:
case WJB_VALUE:
+ /*
+ * If instructed complain about attempts to replace whithin a
+ * scalar value. This happens even when current level is equal to
+ * path_len, because the last path key should also correspond to an
+ * object or an array, not an element or value.
+ */
+ if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot replace existing key"),
+ errdetail("The path assumes key is a composite object, "
+ "but it is a scalar value.")));
+
res = pushJsonbValue(st, r, &v);
break;
default:
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5b5510c4fd..cf0ce0e44c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5134,6 +5134,33 @@ select * from test_jsonb_subscript;
1 | {"a": [null, {"c": [null, null, 1]}]}
(1 row)
+-- trying replace assuming a composite object, but it's an element or a value
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+-- trying replace assuming a composite object, but it's a raw scalar
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
+update test_jsonb_subscript set test_json[0][0] = '1';
+ERROR: cannot replace existing key
+DETAIL: The path assumes key is a composite object, but it is a scalar value.
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
to_tsvector
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 0320db0ea4..1a9d21741f 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1371,6 +1371,23 @@ insert into test_jsonb_subscript values (1, '{"a": []}');
update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1';
select * from test_jsonb_subscript;
+-- trying replace assuming a composite object, but it's an element or a value
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, '{"a": 1}');
+update test_jsonb_subscript set test_json['a']['b'] = '1';
+update test_jsonb_subscript set test_json['a']['b']['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0] = '1';
+update test_jsonb_subscript set test_json['a'][0]['c'] = '1';
+update test_jsonb_subscript set test_json['a'][0][0] = '1';
+
+-- trying replace assuming a composite object, but it's a raw scalar
+
+delete from test_jsonb_subscript;
+insert into test_jsonb_subscript values (1, 'null');
+update test_jsonb_subscript set test_json[0] = '1';
+update test_jsonb_subscript set test_json[0][0] = '1';
+
-- jsonb to tsvector
select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb);
--
2.21.0
--3x42k255okwqcjpg--
^ permalink raw reply [nested|flat] 108+ messages in thread
* failure in 019_replslot_limit
@ 2023-04-05 18:48 Andres Freund <[email protected]>
2023-04-05 18:55 ` Re: failure in 019_replslot_limit Andres Freund <[email protected]>
0 siblings, 1 reply; 108+ messages in thread
From: Andres Freund @ 2023-04-05 18:48 UTC (permalink / raw)
To: pgsql-hackers; Alvaro Herrera <[email protected]>; Kyotaro HORIGUCHI <[email protected]>
Hi,
I just saw the following failure:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=mylodon&dt=2023-04-05%2017%3A47%3A03
after a commit of mine. The symptoms look unrelated though.
[17:54:42.188](258.345s) # poll_query_until timed out executing this query:
# SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep3'
# expecting this output:
# lost
# last actual query output:
# unreserved
# with stderr:
timed out waiting for slot to be lost at /home/bf/bf-build/mylodon/HEAD/pgsql/src/test/recovery/t/019_replslot_limit.pl line 400.
We're expecting "lost" but are getting "unreserved".
At first I though this was just a race - it's not guaranteed that a checkpoint
to remove the WAL files occurs anytime soon.
But there might be something else going on - in this case a checkpoint
started, but never finished:
2023-04-05 17:50:23.786 UTC [345177] 019_replslot_limit.pl LOG: statement: SELECT pg_switch_wal();
2023-04-05 17:50:23.787 UTC [342404] LOG: checkpoints are occurring too frequently (2 seconds apart)
2023-04-05 17:50:23.787 UTC [342404] HINT: Consider increasing the configuration parameter "max_wal_size".
2023-04-05 17:50:23.787 UTC [342404] LOG: checkpoint starting: wal
2023-04-05 17:50:23.837 UTC [345264] 019_replslot_limit.pl LOG: statement: CREATE TABLE t ();
2023-04-05 17:50:23.839 UTC [345264] 019_replslot_limit.pl LOG: statement: DROP TABLE t;
2023-04-05 17:50:23.840 UTC [345264] 019_replslot_limit.pl LOG: statement: SELECT pg_switch_wal();
2023-04-05 17:50:23.841 UTC [342404] LOG: terminating process 344783 to release replication slot "rep3"
2023-04-05 17:50:23.841 UTC [342404] DETAIL: The slot's restart_lsn 0/7000D8 exceeds the limit by 1048360 bytes.
2023-04-05 17:50:23.841 UTC [342404] HINT: You might need to increase max_slot_wal_keep_size.
2023-04-05 17:50:23.862 UTC [344783] standby_3 FATAL: terminating connection due to administrator command
2023-04-05 17:50:23.862 UTC [344783] standby_3 STATEMENT: START_REPLICATION SLOT "rep3" 0/700000 TIMELINE 1
2023-04-05 17:50:23.893 UTC [345314] 019_replslot_limit.pl LOG: statement: SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep3'
[many repetitions of the above, just differing in time and pid]
2023-04-05 17:54:42.084 UTC [491062] 019_replslot_limit.pl LOG: statement: SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep3'
2023-04-05 17:54:42.200 UTC [342365] LOG: received immediate shutdown request
2023-04-05 17:54:42.229 UTC [342365] LOG: database system is shut down
Note that a checkpoint started at "17:50:23.787", but didn't finish before the
database was shut down. As far as I can tell, this can not be caused by
checkpoint_timeout, because by the time we get to invalidating replication
slots, we already did CheckPointBuffers(), and that's the only thing that
delays based on checkpoint_timeout.
ISTM that this indicates that checkpointer got stuck after signalling
344783.
Do you see any other explanation?
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 108+ messages in thread
* Re: failure in 019_replslot_limit
2023-04-05 18:48 failure in 019_replslot_limit Andres Freund <[email protected]>
@ 2023-04-05 18:55 ` Andres Freund <[email protected]>
2023-04-06 03:09 ` Re: failure in 019_replslot_limit Kyotaro Horiguchi <[email protected]>
0 siblings, 1 reply; 108+ messages in thread
From: Andres Freund @ 2023-04-05 18:55 UTC (permalink / raw)
To: pgsql-hackers; Alvaro Herrera <[email protected]>; Kyotaro HORIGUCHI <[email protected]>
Hi,
On 2023-04-05 11:48:53 -0700, Andres Freund wrote:
> Note that a checkpoint started at "17:50:23.787", but didn't finish before the
> database was shut down. As far as I can tell, this can not be caused by
> checkpoint_timeout, because by the time we get to invalidating replication
> slots, we already did CheckPointBuffers(), and that's the only thing that
> delays based on checkpoint_timeout.
>
> ISTM that this indicates that checkpointer got stuck after signalling
> 344783.
>
> Do you see any other explanation?
This all sounded vaguely familiar. After a bit bit of digging I found this:
https://postgr.es/m/20220223014855.4lsddr464i7mymk2%40alap3.anarazel.de
Which seems like it plausibly explains the failed test?
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 108+ messages in thread
* Re: failure in 019_replslot_limit
2023-04-05 18:48 failure in 019_replslot_limit Andres Freund <[email protected]>
2023-04-05 18:55 ` Re: failure in 019_replslot_limit Andres Freund <[email protected]>
@ 2023-04-06 03:09 ` Kyotaro Horiguchi <[email protected]>
0 siblings, 0 replies; 108+ messages in thread
From: Kyotaro Horiguchi @ 2023-04-06 03:09 UTC (permalink / raw)
To: [email protected]; +Cc: pgsql-hackers; [email protected]; [email protected]
At Wed, 5 Apr 2023 11:55:14 -0700, Andres Freund <[email protected]> wrote in
> Hi,
>
> On 2023-04-05 11:48:53 -0700, Andres Freund wrote:
> > Note that a checkpoint started at "17:50:23.787", but didn't finish before the
> > database was shut down. As far as I can tell, this can not be caused by
> > checkpoint_timeout, because by the time we get to invalidating replication
> > slots, we already did CheckPointBuffers(), and that's the only thing that
> > delays based on checkpoint_timeout.
> >
> > ISTM that this indicates that checkpointer got stuck after signalling
> > 344783.
> >
> > Do you see any other explanation?
>
> This all sounded vaguely familiar. After a bit bit of digging I found this:
>
> https://postgr.es/m/20220223014855.4lsddr464i7mymk2%40alap3.anarazel.de
>
> Which seems like it plausibly explains the failed test?
As my understanding, ConditionVariableSleep() can experience random
wake-ups and ReplicationSlotControlLock doesn't prevent slot
release. So, I can imagine a situation where that blocking might
happen. If the call ConditionVariableSleep(&s->active_cv) wakes up
unexpectedly due to a latch set for reasons other than the CV
broadcast, and the target process releases the slot between fetching
active_pid in the loop and the following call to
ConditionVariablePrepareToSleep(), the CV broadcast triggered by the
slot release might be missed. If that's the case, we'll need to check
active_pid again after the calling ConditionVariablePrepareToSleep().
Does this make sense?
regards.
--
Kyotaro Horiguchi
NTT Open Source Software Center
^ permalink raw reply [nested|flat] 108+ messages in thread
end of thread, other threads:[~2023-04-06 03:09 UTC | newest]
Thread overview: 108+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2021-01-07 08:04 [PATCH v44 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v44 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v45 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v44 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v45 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v44 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v45 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v45 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v45 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v44 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v45 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v44 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v44 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v44 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v45 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v44 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v45 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v44 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v45 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v45 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v44 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v45 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v44 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v45 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v45 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v44 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v45 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v44 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v45 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v44 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v44 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v45 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v44 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v44 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v45 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v45 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v44 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v44 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v45 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v45 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v45 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-07 08:04 [PATCH v44 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v48 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v46 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v49 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v46 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v48 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v46 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v48 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v48 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v46 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v46 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v46 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v49 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v48 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v49 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v49 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v46 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v49 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v48 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v48 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v48 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v48 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v46 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v48 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v49 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v46 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v48 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v48 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v48 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v46 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v49 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v48 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v48 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v49 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v49 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v49 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v48 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v46 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v46 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v48 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v49 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v46 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v49 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v46 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v49 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v49 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v49 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v46 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v49 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v46 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v48 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v46 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v49 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v48 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v49 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v48 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v46 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v46 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v49 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v46 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v48 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v49 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v46 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2021-01-20 15:53 [PATCH v49 3/3] Replace assuming a composite object on a scalar Dmitrii Dolgov <[email protected]>
2023-04-05 18:48 failure in 019_replslot_limit Andres Freund <[email protected]>
2023-04-05 18:55 ` Re: failure in 019_replslot_limit Andres Freund <[email protected]>
2023-04-06 03:09 ` Re: failure in 019_replslot_limit Kyotaro Horiguchi <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox