public inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH v44 3/3] Replace assuming a composite object on a scalar
124+ messages / 5 participants
[nested] [flat]

* [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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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] 124+ 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; 124+ 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] 124+ 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; 124+ 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] 124+ 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; 124+ 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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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] 124+ 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; 124+ 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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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] 124+ 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; 124+ 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] 124+ 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; 124+ 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] 124+ 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; 124+ 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] 124+ 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; 124+ 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] 124+ 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; 124+ 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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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] 124+ 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; 124+ 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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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] 124+ 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; 124+ 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] 124+ 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; 124+ 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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ 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; 124+ 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-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"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] 124+ messages in thread

* PostgreSQL and OpenSSL 4.0.0
@ 2026-04-16 13:32 Daniel Gustafsson <[email protected]>
  2026-04-17 21:50 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  0 siblings, 1 reply; 124+ messages in thread

From: Daniel Gustafsson @ 2026-04-16 13:32 UTC (permalink / raw)
  To: PostgreSQL-development <[email protected]>

With OpenSSL 4.0.0 shipping GA a few days ago I tried compiling PostgreSQL
against it and run the tests.  Unsurpisingly the test pass just fine and it
compiles without error since they aren't removing any API's we use (they are
deprecating more APIs we use, more on that in later threads).  There are
however a number of warnings since they changed constness on a number of API's.
Sadly, just adopting const cause similar warnings on OpenSSL 1.1.1 and LibreSSL
so we need to do uglier tricks with casting away constness.

The attached patch, while not pretty, allows libpq and sslinfo to build without
warnings on OpenSSL 1.1.1 through 4.0.0 as well as on LibreSSL (and there is
quite some variability in constness across all these API versions).

--
Daniel Gustafsson



Attachments:

  [application/octet-stream] 0001-ssl-Declare-variables-const-as-per-OpenSSL-4-API-upd.patch (6.4K, ../../[email protected]/2-0001-ssl-Declare-variables-const-as-per-OpenSSL-4-API-upd.patch)
  download | inline diff:
From 8c878a8c98816bd47c6e7f39e86f5e477fc230ec Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Thu, 16 Apr 2026 15:20:13 +0200
Subject: [PATCH] ssl: Declare variables const as per OpenSSL 4 API updates

OpenSSL 4.0.0 changed some parameters and return values to const,
update our declarations and subsequently cast away constness from
a few callsites to make libpq build without warnings with OpenSSL
1.1.1 through 4.0.0.  as well as LibreSSL.
---
 contrib/sslinfo/sslinfo.c                | 20 ++++++++++----------
 src/backend/libpq/be-secure-openssl.c    | 14 +++++++-------
 src/interfaces/libpq/fe-secure-openssl.c |  9 +++++----
 3 files changed, 22 insertions(+), 21 deletions(-)

diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 2b9eb90b093..c4ae847880d 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -24,8 +24,8 @@ PG_MODULE_MAGIC_EXT(
 					.version = PG_VERSION
 );
 
-static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum ASN1_STRING_to_text(ASN1_STRING *str);
+static Datum X509_NAME_field_to_text(const X509_NAME *name, text *fieldName);
+static Datum ASN1_STRING_to_text(const ASN1_STRING *str);
 
 /*
  * Function context for data persisting over repeated calls.
@@ -148,7 +148,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
  * function.
  */
 static Datum
-ASN1_STRING_to_text(ASN1_STRING *str)
+ASN1_STRING_to_text(const ASN1_STRING *str)
 {
 	BIO		   *membuf;
 	size_t		size;
@@ -194,12 +194,12 @@ ASN1_STRING_to_text(ASN1_STRING *str)
  * part of name
  */
 static Datum
-X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
+X509_NAME_field_to_text(const X509_NAME *name, text *fieldName)
 {
 	char	   *string_fieldname;
 	int			nid,
 				index;
-	ASN1_STRING *data;
+	const ASN1_STRING *data;
 
 	string_fieldname = text_to_cstring(fieldName);
 	nid = OBJ_txt2nid(string_fieldname);
@@ -209,7 +209,7 @@ X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
 				 errmsg("invalid X.509 field name: \"%s\"",
 						string_fieldname)));
 	pfree(string_fieldname);
-	index = X509_NAME_get_index_by_NID(name, nid, -1);
+	index = X509_NAME_get_index_by_NID(unconstify(X509_NAME *, name), nid, -1);
 	if (index < 0)
 		return (Datum) 0;
 	data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, index));
@@ -421,8 +421,8 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 		HeapTuple	tuple;
 		Datum		result;
 		BIO		   *membuf;
-		X509_EXTENSION *ext;
-		ASN1_OBJECT *obj;
+		const X509_EXTENSION *ext;
+		const ASN1_OBJECT *obj;
 		int			nid;
 		int			len;
 
@@ -435,7 +435,7 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 
 		/* Get the extension from the certificate */
 		ext = X509_get_ext(cert, call_cntr);
-		obj = X509_EXTENSION_get_object(ext);
+		obj = X509_EXTENSION_get_object(unconstify(X509_EXTENSION *, ext));
 
 		/* Get the extension name */
 		nid = OBJ_obj2nid(obj);
@@ -448,7 +448,7 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 		nulls[0] = false;
 
 		/* Get the extension value */
-		if (X509V3_EXT_print(membuf, ext, 0, 0) <= 0)
+		if (X509V3_EXT_print(membuf, unconstify(X509_EXTENSION *, ext), 0, 0) <= 0)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("could not print extension value in certificate at position %d",
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index a3e222f3a3d..3bf96022e1b 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -106,7 +106,7 @@ static void host_context_cleanup_cb(void *arg);
 static int	sni_clienthello_cb(SSL *ssl, int *al, void *arg);
 #endif
 
-static char *X509_NAME_to_cstring(X509_NAME *name);
+static char *X509_NAME_to_cstring(const X509_NAME *name);
 
 static SSL_CTX *SSL_context = NULL;
 static MemoryContext SSL_hosts_memcxt = NULL;
@@ -1071,18 +1071,18 @@ aloop:
 	if (port->peer != NULL)
 	{
 		int			len;
-		X509_NAME  *x509name = X509_get_subject_name(port->peer);
+		const X509_NAME *x509name = X509_get_subject_name(port->peer);
 		char	   *peer_dn;
 		BIO		   *bio = NULL;
 		BUF_MEM    *bio_buf = NULL;
 
-		len = X509_NAME_get_text_by_NID(x509name, NID_commonName, NULL, 0);
+		len = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, NULL, 0);
 		if (len != -1)
 		{
 			char	   *peer_cn;
 
 			peer_cn = MemoryContextAlloc(TopMemoryContext, len + 1);
-			r = X509_NAME_get_text_by_NID(x509name, NID_commonName, peer_cn,
+			r = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, peer_cn,
 										  len + 1);
 			peer_cn[len] = '\0';
 			if (r != len)
@@ -2333,14 +2333,14 @@ be_tls_get_certificate_hash(Port *port, size_t *len)
  *
  */
 static char *
-X509_NAME_to_cstring(X509_NAME *name)
+X509_NAME_to_cstring(const X509_NAME *name)
 {
 	BIO		   *membuf = BIO_new(BIO_s_mem());
 	int			i,
 				nid,
 				count = X509_NAME_entry_count(name);
-	X509_NAME_ENTRY *e;
-	ASN1_STRING *v;
+	const X509_NAME_ENTRY *e;
+	const ASN1_STRING *v;
 	const char *field_name;
 	size_t		size;
 	char		nullterm;
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index fbd3c63fb5d..6b44eeb68eb 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -67,7 +67,7 @@
 
 static int	verify_cb(int ok, X509_STORE_CTX *ctx);
 static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
-															  ASN1_STRING *name_entry,
+															  const ASN1_STRING *name_entry,
 															  char **store_name);
 static int	openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
 															ASN1_OCTET_STRING *addr_entry,
@@ -467,7 +467,8 @@ cert_cb(SSL *ssl, void *arg)
  * into a plain C string.
  */
 static int
-openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
+openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
+												  const ASN1_STRING *name_entry,
 												  char **store_name)
 {
 	int			len;
@@ -650,14 +651,14 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	 */
 	if (check_cn)
 	{
-		X509_NAME  *subject_name;
+		const X509_NAME *subject_name;
 
 		subject_name = X509_get_subject_name(conn->peer);
 		if (subject_name != NULL)
 		{
 			int			cn_index;
 
-			cn_index = X509_NAME_get_index_by_NID(subject_name,
+			cn_index = X509_NAME_get_index_by_NID(unconstify(X509_NAME *, subject_name),
 												  NID_commonName, -1);
 			if (cn_index >= 0)
 			{
-- 
2.39.3 (Apple Git-146)



^ permalink  raw  reply  [nested|flat] 124+ messages in thread

* Re: PostgreSQL and OpenSSL 4.0.0
  2026-04-16 13:32 PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
@ 2026-04-17 21:50 ` Michael Paquier <[email protected]>
  2026-05-07 13:44   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  0 siblings, 1 reply; 124+ messages in thread

From: Michael Paquier @ 2026-04-17 21:50 UTC (permalink / raw)
  To: Daniel Gustafsson <[email protected]>; +Cc: PostgreSQL-development <[email protected]>

On Thu, Apr 16, 2026 at 03:32:54PM +0200, Daniel Gustafsson wrote:
> The attached patch, while not pretty, allows libpq and sslinfo to build without
> warnings on OpenSSL 1.1.1 through 4.0.0 as well as on LibreSSL (and there is
> quite some variability in constness across all these API versions).

Thanks for that.  That is super fast.

This is likely going to require a backpatch at some point, right?
What's the impact of the blast in branches where we need to support
OpenSSL down to 1.0.1, which is the minimum version in REL_14_STABLE?
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 124+ messages in thread

* Re: PostgreSQL and OpenSSL 4.0.0
  2026-04-16 13:32 PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-04-17 21:50 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
@ 2026-05-07 13:44   ` Daniel Gustafsson <[email protected]>
  2026-05-07 19:32     ` Re: PostgreSQL and OpenSSL 4.0.0 Cary Huang <[email protected]>
  2026-05-07 19:51     ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-07 22:13     ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  0 siblings, 3 replies; 124+ messages in thread

From: Daniel Gustafsson @ 2026-05-07 13:44 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: PostgreSQL-development <[email protected]>

> On 17 Apr 2026, at 23:50, Michael Paquier <[email protected]> wrote:
> 
> On Thu, Apr 16, 2026 at 03:32:54PM +0200, Daniel Gustafsson wrote:
>> The attached patch, while not pretty, allows libpq and sslinfo to build without
>> warnings on OpenSSL 1.1.1 through 4.0.0 as well as on LibreSSL (and there is
>> quite some variability in constness across all these API versions).
> 
> Thanks for that.  That is super fast.
> 
> This is likely going to require a backpatch at some point, right?
> What's the impact of the blast in branches where we need to support
> OpenSSL down to 1.0.1, which is the minimum version in REL_14_STABLE?

Indeed, we probably want to backpatch this at some point since OpenSSL 4 is
equally likely to be used regardless of which branch users compile.  Whether we
want to apply this already before 19 goes beta I'll leave for the RMT to
decide.

For 14 through master the attached compiles without warnings and tests green on
all the supported versions of OpenSSL and LibreSSL.  That being said, I'm not
sure that we want to go all the way to 14 since if something does break, we
can't really go around fixing it - I think amending the docs in 14 stating that
OpenSSL 3.6 is the highest supported version is a better solution.

--
Daniel Gustafsson



Attachments:

  [application/octet-stream] vmaster--18-0001-Support-OpenSSL-4.patch (8.9K, ../../[email protected]/2-vmaster--18-0001-Support-OpenSSL-4.patch)
  download | inline diff:
From 585563a1559666925acf125ee30f4ff73e27ca8e Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Thu, 16 Apr 2026 15:20:13 +0200
Subject: [PATCH vmaster] Support OpenSSL 4

OpenSSL 4.0.0 changed some parameters and returnvalues to const, so
we need to update our declarations and subsequently cast away const-
ness from a few callsites to make libpq build without warnings. This
is tested with OpenSSL 1.1.1 through 4.0.0 as well as with LibreSSL.

There is also an errormessage change in OpenSSL 4.0.0 which needs to
be covered by our testharness.

Author: Daniel Gustafsson <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
 contrib/sslinfo/sslinfo.c                | 20 ++++++++++----------
 src/backend/libpq/be-secure-openssl.c    | 14 +++++++-------
 src/interfaces/libpq/fe-secure-openssl.c |  9 +++++----
 src/test/ssl/t/001_ssltests.pl           |  6 +++---
 4 files changed, 25 insertions(+), 24 deletions(-)

diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 2b9eb90b093..c4ae847880d 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -24,8 +24,8 @@ PG_MODULE_MAGIC_EXT(
 					.version = PG_VERSION
 );
 
-static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum ASN1_STRING_to_text(ASN1_STRING *str);
+static Datum X509_NAME_field_to_text(const X509_NAME *name, text *fieldName);
+static Datum ASN1_STRING_to_text(const ASN1_STRING *str);
 
 /*
  * Function context for data persisting over repeated calls.
@@ -148,7 +148,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
  * function.
  */
 static Datum
-ASN1_STRING_to_text(ASN1_STRING *str)
+ASN1_STRING_to_text(const ASN1_STRING *str)
 {
 	BIO		   *membuf;
 	size_t		size;
@@ -194,12 +194,12 @@ ASN1_STRING_to_text(ASN1_STRING *str)
  * part of name
  */
 static Datum
-X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
+X509_NAME_field_to_text(const X509_NAME *name, text *fieldName)
 {
 	char	   *string_fieldname;
 	int			nid,
 				index;
-	ASN1_STRING *data;
+	const ASN1_STRING *data;
 
 	string_fieldname = text_to_cstring(fieldName);
 	nid = OBJ_txt2nid(string_fieldname);
@@ -209,7 +209,7 @@ X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
 				 errmsg("invalid X.509 field name: \"%s\"",
 						string_fieldname)));
 	pfree(string_fieldname);
-	index = X509_NAME_get_index_by_NID(name, nid, -1);
+	index = X509_NAME_get_index_by_NID(unconstify(X509_NAME *, name), nid, -1);
 	if (index < 0)
 		return (Datum) 0;
 	data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, index));
@@ -421,8 +421,8 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 		HeapTuple	tuple;
 		Datum		result;
 		BIO		   *membuf;
-		X509_EXTENSION *ext;
-		ASN1_OBJECT *obj;
+		const X509_EXTENSION *ext;
+		const ASN1_OBJECT *obj;
 		int			nid;
 		int			len;
 
@@ -435,7 +435,7 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 
 		/* Get the extension from the certificate */
 		ext = X509_get_ext(cert, call_cntr);
-		obj = X509_EXTENSION_get_object(ext);
+		obj = X509_EXTENSION_get_object(unconstify(X509_EXTENSION *, ext));
 
 		/* Get the extension name */
 		nid = OBJ_obj2nid(obj);
@@ -448,7 +448,7 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 		nulls[0] = false;
 
 		/* Get the extension value */
-		if (X509V3_EXT_print(membuf, ext, 0, 0) <= 0)
+		if (X509V3_EXT_print(membuf, unconstify(X509_EXTENSION *, ext), 0, 0) <= 0)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("could not print extension value in certificate at position %d",
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index b978497b5d4..8a06fb11ec3 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -106,7 +106,7 @@ static void host_context_cleanup_cb(void *arg);
 static int	sni_clienthello_cb(SSL *ssl, int *al, void *arg);
 #endif
 
-static char *X509_NAME_to_cstring(X509_NAME *name);
+static char *X509_NAME_to_cstring(const X509_NAME *name);
 
 static SSL_CTX *SSL_context = NULL;
 static MemoryContext SSL_hosts_memcxt = NULL;
@@ -1071,18 +1071,18 @@ aloop:
 	if (port->peer != NULL)
 	{
 		int			len;
-		X509_NAME  *x509name = X509_get_subject_name(port->peer);
+		const X509_NAME *x509name = X509_get_subject_name(port->peer);
 		char	   *peer_dn;
 		BIO		   *bio = NULL;
 		BUF_MEM    *bio_buf = NULL;
 
-		len = X509_NAME_get_text_by_NID(x509name, NID_commonName, NULL, 0);
+		len = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, NULL, 0);
 		if (len != -1)
 		{
 			char	   *peer_cn;
 
 			peer_cn = MemoryContextAlloc(TopMemoryContext, len + 1);
-			r = X509_NAME_get_text_by_NID(x509name, NID_commonName, peer_cn,
+			r = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, peer_cn,
 										  len + 1);
 			peer_cn[len] = '\0';
 			if (r != len)
@@ -2333,14 +2333,14 @@ be_tls_get_certificate_hash(Port *port, size_t *len)
  *
  */
 static char *
-X509_NAME_to_cstring(X509_NAME *name)
+X509_NAME_to_cstring(const X509_NAME *name)
 {
 	BIO		   *membuf = BIO_new(BIO_s_mem());
 	int			i,
 				nid,
 				count = X509_NAME_entry_count(name);
-	X509_NAME_ENTRY *e;
-	ASN1_STRING *v;
+	const X509_NAME_ENTRY *e;
+	const ASN1_STRING *v;
 	const char *field_name;
 	size_t		size;
 	char		nullterm;
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index fbd3c63fb5d..6b44eeb68eb 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -67,7 +67,7 @@
 
 static int	verify_cb(int ok, X509_STORE_CTX *ctx);
 static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
-															  ASN1_STRING *name_entry,
+															  const ASN1_STRING *name_entry,
 															  char **store_name);
 static int	openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
 															ASN1_OCTET_STRING *addr_entry,
@@ -467,7 +467,8 @@ cert_cb(SSL *ssl, void *arg)
  * into a plain C string.
  */
 static int
-openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
+openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
+												  const ASN1_STRING *name_entry,
 												  char **store_name)
 {
 	int			len;
@@ -650,14 +651,14 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	 */
 	if (check_cn)
 	{
-		X509_NAME  *subject_name;
+		const X509_NAME *subject_name;
 
 		subject_name = X509_get_subject_name(conn->peer);
 		if (subject_name != NULL)
 		{
 			int			cn_index;
 
-			cn_index = X509_NAME_get_index_by_NID(subject_name,
+			cn_index = X509_NAME_get_index_by_NID(unconstify(X509_NAME *, subject_name),
 												  NID_commonName, -1);
 			if (cn_index >= 0)
 			{
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 0af887caa63..01f3573e1fd 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -885,7 +885,7 @@ $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt "
 	  . sslkey('client-revoked.key'),
 	"certificate authorization fails with revoked client cert",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|,
+	expected_stderr => qr!SSL error: (ssl[a-z0-9/]*|tls) alert certificate revoked!,
 	log_like => [
 		qr{Client certificate verification failed at depth 0: certificate revoked},
 		qr{Failed certificate data \(unverified\): subject "/CN=ssltestuser", serial number \d+, issuer "/CN=Test CA for PostgreSQL SSL regression test client certs"},
@@ -987,7 +987,7 @@ $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt "
 	  . sslkey('client-revoked.key'),
 	"certificate authorization fails with revoked client cert with server-side CRL directory",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|,
+	expected_stderr => qr!SSL error: (ssl[a-z0-9/]*|tls) alert certificate revoked!,
 	log_like => [
 		qr{Client certificate verification failed at depth 0: certificate revoked},
 		qr{Failed certificate data \(unverified\): subject "/CN=ssltestuser", serial number \d+, issuer "/CN=Test CA for PostgreSQL SSL regression test client certs"},
@@ -998,7 +998,7 @@ $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked-utf8.crt "
 	  . sslkey('client-revoked-utf8.key'),
 	"certificate authorization fails with revoked UTF-8 client cert with server-side CRL directory",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|,
+	expected_stderr => qr!SSL error: (ssl[a-z0-9/]*|tls) alert certificate revoked!,
 	log_like => [
 		qr{Client certificate verification failed at depth 0: certificate revoked},
 		qr{Failed certificate data \(unverified\): subject "/CN=\\xce\\x9f\\xce\\xb4\\xcf\\x85\\xcf\\x83\\xcf\\x83\\xce\\xad\\xce\\xb1\\xcf\\x82", serial number \d+, issuer "/CN=Test CA for PostgreSQL SSL regression test client certs"},
-- 
2.39.3 (Apple Git-146)



  [application/octet-stream] vREL_17--REL_16-0001-Support-OpenSSL-4.patch (10.9K, ../../[email protected]/3-vREL_17--REL_16-0001-Support-OpenSSL-4.patch)
  download | inline diff:
From 4964aaf322bfd55aa27065d2e732c6e69775e0bf Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Thu, 7 May 2026 10:46:18 +0200
Subject: [PATCH vREL_17] Support OpenSSL 4

OpenSSL 4.0.0 changed some parameters and returnvalues to const, so
we need to update our declarations and subsequently cast away const-
ness from a few callsites to make libpq build without warnings. This
is tested with OpenSSL 1.1.1 through 4.0.0 as well as with LibreSSL.

There is also an errormessage change in OpenSSL 4.0.0 which needs to
be covered by our testharness.

Author: Daniel Gustafsson <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
 contrib/sslinfo/sslinfo.c                | 24 +++++++++++-----------
 src/backend/libpq/be-secure-openssl.c    | 26 ++++++++++++------------
 src/interfaces/libpq/fe-secure-openssl.c | 13 ++++++------
 src/test/ssl/t/001_ssltests.pl           |  4 ++--
 4 files changed, 34 insertions(+), 33 deletions(-)

diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5fd46b98741..4251ccfd174 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -32,8 +32,8 @@
 
 PG_MODULE_MAGIC;
 
-static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum ASN1_STRING_to_text(ASN1_STRING *str);
+static Datum X509_NAME_field_to_text(const X509_NAME *name, text *fieldName);
+static Datum ASN1_STRING_to_text(const ASN1_STRING *str);
 
 /*
  * Function context for data persisting over repeated calls.
@@ -156,7 +156,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
  * function.
  */
 static Datum
-ASN1_STRING_to_text(ASN1_STRING *str)
+ASN1_STRING_to_text(const ASN1_STRING *str)
 {
 	BIO		   *membuf;
 	size_t		size;
@@ -171,7 +171,7 @@ ASN1_STRING_to_text(ASN1_STRING *str)
 				(errcode(ERRCODE_OUT_OF_MEMORY),
 				 errmsg("could not create OpenSSL BIO structure")));
 	(void) BIO_set_close(membuf, BIO_CLOSE);
-	ASN1_STRING_print_ex(membuf, str,
+	ASN1_STRING_print_ex(membuf, unconstify(ASN1_STRING *, str),
 						 ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
 						  | ASN1_STRFLGS_UTF8_CONVERT));
 	/* ensure null termination of the BIO's content */
@@ -202,12 +202,12 @@ ASN1_STRING_to_text(ASN1_STRING *str)
  * part of name
  */
 static Datum
-X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
+X509_NAME_field_to_text(const X509_NAME *name, text *fieldName)
 {
 	char	   *string_fieldname;
 	int			nid,
 				index;
-	ASN1_STRING *data;
+	const ASN1_STRING *data;
 
 	string_fieldname = text_to_cstring(fieldName);
 	nid = OBJ_txt2nid(string_fieldname);
@@ -217,10 +217,10 @@ X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
 				 errmsg("invalid X.509 field name: \"%s\"",
 						string_fieldname)));
 	pfree(string_fieldname);
-	index = X509_NAME_get_index_by_NID(name, nid, -1);
+	index = X509_NAME_get_index_by_NID(unconstify(X509_NAME *, name), nid, -1);
 	if (index < 0)
 		return (Datum) 0;
-	data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, index));
+	data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(unconstify(X509_NAME *, name), index));
 	return ASN1_STRING_to_text(data);
 }
 
@@ -429,8 +429,8 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 		HeapTuple	tuple;
 		Datum		result;
 		BIO		   *membuf;
-		X509_EXTENSION *ext;
-		ASN1_OBJECT *obj;
+		const X509_EXTENSION *ext;
+		const ASN1_OBJECT *obj;
 		int			nid;
 		int			len;
 
@@ -443,7 +443,7 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 
 		/* Get the extension from the certificate */
 		ext = X509_get_ext(cert, call_cntr);
-		obj = X509_EXTENSION_get_object(ext);
+		obj = X509_EXTENSION_get_object(unconstify(X509_EXTENSION *, ext));
 
 		/* Get the extension name */
 		nid = OBJ_obj2nid(obj);
@@ -456,7 +456,7 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 		nulls[0] = false;
 
 		/* Get the extension value */
-		if (X509V3_EXT_print(membuf, ext, 0, 0) <= 0)
+		if (X509V3_EXT_print(membuf, unconstify(X509_EXTENSION *, ext), 0, 0) <= 0)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("could not print extension value in certificate at position %d",
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 486a66b0bf1..8c101528618 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -77,7 +77,7 @@ static bool initialize_dh(SSL_CTX *context, bool isServerStart);
 static bool initialize_ecdh(SSL_CTX *context, bool isServerStart);
 static const char *SSLerrmessage(unsigned long ecode);
 
-static char *X509_NAME_to_cstring(X509_NAME *name);
+static char *X509_NAME_to_cstring(const X509_NAME *name);
 
 static SSL_CTX *SSL_context = NULL;
 static bool SSL_initialized = false;
@@ -634,18 +634,18 @@ aloop:
 	if (port->peer != NULL)
 	{
 		int			len;
-		X509_NAME  *x509name = X509_get_subject_name(port->peer);
+		const X509_NAME *x509name = X509_get_subject_name(port->peer);
 		char	   *peer_dn;
 		BIO		   *bio = NULL;
 		BUF_MEM    *bio_buf = NULL;
 
-		len = X509_NAME_get_text_by_NID(x509name, NID_commonName, NULL, 0);
+		len = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, NULL, 0);
 		if (len != -1)
 		{
 			char	   *peer_cn;
 
 			peer_cn = MemoryContextAlloc(TopMemoryContext, len + 1);
-			r = X509_NAME_get_text_by_NID(x509name, NID_commonName, peer_cn,
+			r = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, peer_cn,
 										  len + 1);
 			peer_cn[len] = '\0';
 			if (r != len)
@@ -689,7 +689,7 @@ aloop:
 		 * which make regular expression matching a bit easier. Also note that
 		 * it prints the Subject fields in reverse order.
 		 */
-		if (X509_NAME_print_ex(bio, x509name, 0, XN_FLAG_RFC2253) == -1 ||
+		if (X509_NAME_print_ex(bio, unconstify(X509_NAME *, x509name), 0, XN_FLAG_RFC2253) == -1 ||
 			BIO_get_mem_ptr(bio, &bio_buf) <= 0)
 		{
 			BIO_free(bio);
@@ -1615,14 +1615,14 @@ be_tls_get_certificate_hash(Port *port, size_t *len)
  *
  */
 static char *
-X509_NAME_to_cstring(X509_NAME *name)
+X509_NAME_to_cstring(const X509_NAME *name)
 {
 	BIO		   *membuf = BIO_new(BIO_s_mem());
 	int			i,
 				nid,
-				count = X509_NAME_entry_count(name);
-	X509_NAME_ENTRY *e;
-	ASN1_STRING *v;
+				count = X509_NAME_entry_count(unconstify(X509_NAME *, name));
+	const X509_NAME_ENTRY *e;
+	const ASN1_STRING *v;
 	const char *field_name;
 	size_t		size;
 	char		nullterm;
@@ -1638,13 +1638,13 @@ X509_NAME_to_cstring(X509_NAME *name)
 	(void) BIO_set_close(membuf, BIO_CLOSE);
 	for (i = 0; i < count; i++)
 	{
-		e = X509_NAME_get_entry(name, i);
-		nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
+		e = X509_NAME_get_entry(unconstify(X509_NAME *, name), i);
+		nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(unconstify(X509_NAME_ENTRY *, e)));
 		if (nid == NID_undef)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("could not get NID for ASN1_OBJECT object")));
-		v = X509_NAME_ENTRY_get_data(e);
+		v = X509_NAME_ENTRY_get_data(unconstify(X509_NAME_ENTRY *, e));
 		field_name = OBJ_nid2sn(nid);
 		if (field_name == NULL)
 			field_name = OBJ_nid2ln(nid);
@@ -1653,7 +1653,7 @@ X509_NAME_to_cstring(X509_NAME *name)
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
 		BIO_printf(membuf, "/%s=", field_name);
-		ASN1_STRING_print_ex(membuf, v,
+		ASN1_STRING_print_ex(membuf, unconstify(ASN1_STRING *, v),
 							 ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
 							  | ASN1_STRFLGS_UTF8_CONVERT));
 	}
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index b6fffd7b9b0..33ce6d5ffe3 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -66,7 +66,7 @@
 
 static int	verify_cb(int ok, X509_STORE_CTX *ctx);
 static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
-															  ASN1_STRING *name_entry,
+															  const ASN1_STRING *name_entry,
 															  char **store_name);
 static int	openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
 															ASN1_OCTET_STRING *addr_entry,
@@ -489,7 +489,8 @@ cert_cb(SSL *ssl, void *arg)
  * into a plain C string.
  */
 static int
-openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
+openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
+												  const ASN1_STRING *name_entry,
 												  char **store_name)
 {
 	int			len;
@@ -508,7 +509,7 @@ openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *nam
 #ifdef HAVE_ASN1_STRING_GET0_DATA
 	namedata = ASN1_STRING_get0_data(name_entry);
 #else
-	namedata = ASN1_STRING_data(name_entry);
+	namedata = ASN1_STRING_data(unconstify(ASN1_STRING *, name_entry));
 #endif
 	len = ASN1_STRING_length(name_entry);
 
@@ -680,14 +681,14 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	 */
 	if (check_cn)
 	{
-		X509_NAME  *subject_name;
+		const X509_NAME *subject_name;
 
 		subject_name = X509_get_subject_name(conn->peer);
 		if (subject_name != NULL)
 		{
 			int			cn_index;
 
-			cn_index = X509_NAME_get_index_by_NID(subject_name,
+			cn_index = X509_NAME_get_index_by_NID(unconstify(X509_NAME *, subject_name),
 												  NID_commonName, -1);
 			if (cn_index >= 0)
 			{
@@ -695,7 +696,7 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
-																	   X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject_name, cn_index)),
+																	   X509_NAME_ENTRY_get_data(X509_NAME_get_entry(unconstify(X509_NAME *, subject_name), cn_index)),
 																	   &common_name);
 
 				if (common_name)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 17dd3964b76..4880d8a75b9 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -773,7 +773,7 @@ $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt "
 	  . sslkey('client-revoked.key'),
 	"certificate authorization fails with revoked client cert",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|,
+	expected_stderr => qr!SSL error: (ssl[a-z0-9/]*|tls) alert certificate revoked!,
 	# temporarily(?) skip this check due to timing issue
 	#	log_like => [
 	#		qr{Client certificate verification failed at depth 0: certificate revoked},
@@ -878,7 +878,7 @@ $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt "
 	  . sslkey('client-revoked.key'),
 	"certificate authorization fails with revoked client cert with server-side CRL directory",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|,
+	expected_stderr => qr!SSL error: (ssl[a-z0-9/]*|tls) alert certificate revoked!,
 	# temporarily(?) skip this check due to timing issue
 	#	log_like => [
 	#		qr{Client certificate verification failed at depth 0: certificate revoked},
-- 
2.39.3 (Apple Git-146)



  [application/octet-stream] vREL_15-0001-Support-OpenSSL-4.patch (9.9K, ../../[email protected]/4-vREL_15-0001-Support-OpenSSL-4.patch)
  download | inline diff:
From 55515764f958ce6c7e1cb212d043584dfa55d6a8 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Thu, 7 May 2026 10:46:18 +0200
Subject: [PATCH vREL_15] Support OpenSSL 4

OpenSSL 4.0.0 changed some parameters and returnvalues to const, so
we need to update our declarations and subsequently cast away const-
ness from a few callsites to make libpq build without warnings. This
is tested with OpenSSL 1.1.1 through 4.0.0 as well as with LibreSSL.

There is also an errormessage change in OpenSSL 4.0.0 which needs to
be covered by our testharness.

Author: Daniel Gustafsson <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
 contrib/sslinfo/sslinfo.c                | 24 +++++++++++-----------
 src/backend/libpq/be-secure-openssl.c    | 26 ++++++++++++------------
 src/interfaces/libpq/fe-secure-openssl.c |  9 ++++----
 src/test/ssl/t/001_ssltests.pl           |  4 ++--
 4 files changed, 32 insertions(+), 31 deletions(-)

diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5fd46b98741..4251ccfd174 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -32,8 +32,8 @@
 
 PG_MODULE_MAGIC;
 
-static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum ASN1_STRING_to_text(ASN1_STRING *str);
+static Datum X509_NAME_field_to_text(const X509_NAME *name, text *fieldName);
+static Datum ASN1_STRING_to_text(const ASN1_STRING *str);
 
 /*
  * Function context for data persisting over repeated calls.
@@ -156,7 +156,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
  * function.
  */
 static Datum
-ASN1_STRING_to_text(ASN1_STRING *str)
+ASN1_STRING_to_text(const ASN1_STRING *str)
 {
 	BIO		   *membuf;
 	size_t		size;
@@ -171,7 +171,7 @@ ASN1_STRING_to_text(ASN1_STRING *str)
 				(errcode(ERRCODE_OUT_OF_MEMORY),
 				 errmsg("could not create OpenSSL BIO structure")));
 	(void) BIO_set_close(membuf, BIO_CLOSE);
-	ASN1_STRING_print_ex(membuf, str,
+	ASN1_STRING_print_ex(membuf, unconstify(ASN1_STRING *, str),
 						 ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
 						  | ASN1_STRFLGS_UTF8_CONVERT));
 	/* ensure null termination of the BIO's content */
@@ -202,12 +202,12 @@ ASN1_STRING_to_text(ASN1_STRING *str)
  * part of name
  */
 static Datum
-X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
+X509_NAME_field_to_text(const X509_NAME *name, text *fieldName)
 {
 	char	   *string_fieldname;
 	int			nid,
 				index;
-	ASN1_STRING *data;
+	const ASN1_STRING *data;
 
 	string_fieldname = text_to_cstring(fieldName);
 	nid = OBJ_txt2nid(string_fieldname);
@@ -217,10 +217,10 @@ X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
 				 errmsg("invalid X.509 field name: \"%s\"",
 						string_fieldname)));
 	pfree(string_fieldname);
-	index = X509_NAME_get_index_by_NID(name, nid, -1);
+	index = X509_NAME_get_index_by_NID(unconstify(X509_NAME *, name), nid, -1);
 	if (index < 0)
 		return (Datum) 0;
-	data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, index));
+	data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(unconstify(X509_NAME *, name), index));
 	return ASN1_STRING_to_text(data);
 }
 
@@ -429,8 +429,8 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 		HeapTuple	tuple;
 		Datum		result;
 		BIO		   *membuf;
-		X509_EXTENSION *ext;
-		ASN1_OBJECT *obj;
+		const X509_EXTENSION *ext;
+		const ASN1_OBJECT *obj;
 		int			nid;
 		int			len;
 
@@ -443,7 +443,7 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 
 		/* Get the extension from the certificate */
 		ext = X509_get_ext(cert, call_cntr);
-		obj = X509_EXTENSION_get_object(ext);
+		obj = X509_EXTENSION_get_object(unconstify(X509_EXTENSION *, ext));
 
 		/* Get the extension name */
 		nid = OBJ_obj2nid(obj);
@@ -456,7 +456,7 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 		nulls[0] = false;
 
 		/* Get the extension value */
-		if (X509V3_EXT_print(membuf, ext, 0, 0) <= 0)
+		if (X509V3_EXT_print(membuf, unconstify(X509_EXTENSION *, ext), 0, 0) <= 0)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("could not print extension value in certificate at position %d",
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index b0492c443ec..96e60a97f04 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -71,7 +71,7 @@ static bool initialize_dh(SSL_CTX *context, bool isServerStart);
 static bool initialize_ecdh(SSL_CTX *context, bool isServerStart);
 static const char *SSLerrmessage(unsigned long ecode);
 
-static char *X509_NAME_to_cstring(X509_NAME *name);
+static char *X509_NAME_to_cstring(const X509_NAME *name);
 
 static SSL_CTX *SSL_context = NULL;
 static bool SSL_initialized = false;
@@ -587,18 +587,18 @@ aloop:
 	if (port->peer != NULL)
 	{
 		int			len;
-		X509_NAME  *x509name = X509_get_subject_name(port->peer);
+		const X509_NAME *x509name = X509_get_subject_name(port->peer);
 		char	   *peer_dn;
 		BIO		   *bio = NULL;
 		BUF_MEM    *bio_buf = NULL;
 
-		len = X509_NAME_get_text_by_NID(x509name, NID_commonName, NULL, 0);
+		len = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, NULL, 0);
 		if (len != -1)
 		{
 			char	   *peer_cn;
 
 			peer_cn = MemoryContextAlloc(TopMemoryContext, len + 1);
-			r = X509_NAME_get_text_by_NID(x509name, NID_commonName, peer_cn,
+			r = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, peer_cn,
 										  len + 1);
 			peer_cn[len] = '\0';
 			if (r != len)
@@ -642,7 +642,7 @@ aloop:
 		 * which make regular expression matching a bit easier. Also note that
 		 * it prints the Subject fields in reverse order.
 		 */
-		if (X509_NAME_print_ex(bio, x509name, 0, XN_FLAG_RFC2253) == -1 ||
+		if (X509_NAME_print_ex(bio, unconstify(X509_NAME *, x509name), 0, XN_FLAG_RFC2253) == -1 ||
 			BIO_get_mem_ptr(bio, &bio_buf) <= 0)
 		{
 			BIO_free(bio);
@@ -1422,14 +1422,14 @@ be_tls_get_certificate_hash(Port *port, size_t *len)
  *
  */
 static char *
-X509_NAME_to_cstring(X509_NAME *name)
+X509_NAME_to_cstring(const X509_NAME *name)
 {
 	BIO		   *membuf = BIO_new(BIO_s_mem());
 	int			i,
 				nid,
-				count = X509_NAME_entry_count(name);
-	X509_NAME_ENTRY *e;
-	ASN1_STRING *v;
+				count = X509_NAME_entry_count(unconstify(X509_NAME *, name));
+	const X509_NAME_ENTRY *e;
+	const ASN1_STRING *v;
 	const char *field_name;
 	size_t		size;
 	char		nullterm;
@@ -1445,13 +1445,13 @@ X509_NAME_to_cstring(X509_NAME *name)
 	(void) BIO_set_close(membuf, BIO_CLOSE);
 	for (i = 0; i < count; i++)
 	{
-		e = X509_NAME_get_entry(name, i);
-		nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
+		e = X509_NAME_get_entry(unconstify(X509_NAME *, name), i);
+		nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(unconstify(X509_NAME_ENTRY *, e)));
 		if (nid == NID_undef)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("could not get NID for ASN1_OBJECT object")));
-		v = X509_NAME_ENTRY_get_data(e);
+		v = X509_NAME_ENTRY_get_data(unconstify(X509_NAME_ENTRY *, e));
 		field_name = OBJ_nid2sn(nid);
 		if (field_name == NULL)
 			field_name = OBJ_nid2ln(nid);
@@ -1460,7 +1460,7 @@ X509_NAME_to_cstring(X509_NAME *name)
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
 		BIO_printf(membuf, "/%s=", field_name);
-		ASN1_STRING_print_ex(membuf, v,
+		ASN1_STRING_print_ex(membuf, unconstify(ASN1_STRING *, v),
 							 ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
 							  | ASN1_STRFLGS_UTF8_CONVERT));
 	}
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 50d14eac0ee..d22b5279b12 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -70,7 +70,7 @@
 
 static int	verify_cb(int ok, X509_STORE_CTX *ctx);
 static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
-															  ASN1_STRING *name,
+															  const ASN1_STRING *name,
 															  char **store_name);
 static int	openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
 															ASN1_OCTET_STRING *addr_entry,
@@ -490,7 +490,8 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
  * into a plain C string.
  */
 static int
-openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
+openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
+												  const ASN1_STRING *name_entry,
 												  char **store_name)
 {
 	int			len;
@@ -683,14 +684,14 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	 */
 	if (check_cn)
 	{
-		X509_NAME  *subject_name;
+		const X509_NAME *subject_name;
 
 		subject_name = X509_get_subject_name(conn->peer);
 		if (subject_name != NULL)
 		{
 			int			cn_index;
 
-			cn_index = X509_NAME_get_index_by_NID(subject_name,
+			cn_index = X509_NAME_get_index_by_NID(unconstify(X509_NAME *, subject_name),
 												  NID_commonName, -1);
 			if (cn_index >= 0)
 			{
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index c570b48a1bd..756745b7bec 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -682,7 +682,7 @@ $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt "
 	  . sslkey('client-revoked.key'),
 	"certificate authorization fails with revoked client cert",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|,
+	expected_stderr => qr!SSL error: (ssl[a-z0-9/]*|tls) alert certificate revoked!,
 	# revoked certificates should not authenticate the user
 	log_unlike => [qr/connection authenticated:/],);
 
@@ -743,6 +743,6 @@ $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt "
 	  . sslkey('client-revoked.key'),
 	"certificate authorization fails with revoked client cert with server-side CRL directory",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|);
+	expected_stderr => qr!SSL error: (ssl[a-z0-9/]*|tls) alert certificate revoked!);
 
 done_testing();
-- 
2.39.3 (Apple Git-146)



  [application/octet-stream] vREL_14-0001-Support-OpenSSL-4.patch (10.0K, ../../[email protected]/5-vREL_14-0001-Support-OpenSSL-4.patch)
  download | inline diff:
From e6d002430017de95107283cf89b61c1d5cdccf9c Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Thu, 7 May 2026 10:46:18 +0200
Subject: [PATCH vREL_14] Support OpenSSL 4

OpenSSL 4.0.0 changed some parameters and returnvalues to const, so
we need to update our declarations and subsequently cast away const-
ness from a few callsites to make libpq build without warnings. This
is tested with OpenSSL 1.1.1 through 4.0.0 as well as with LibreSSL.

There is also an errormessage change in OpenSSL 4.0.0 which needs to
be covered by our testharness.

Author: Daniel Gustafsson <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
 contrib/sslinfo/sslinfo.c                | 24 +++++++++++-----------
 src/backend/libpq/be-secure-openssl.c    | 26 ++++++++++++------------
 src/interfaces/libpq/fe-secure-openssl.c |  9 ++++----
 src/test/ssl/t/001_ssltests.pl           |  4 ++--
 4 files changed, 32 insertions(+), 31 deletions(-)

diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..4f8a118bc9 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -21,8 +21,8 @@
 
 PG_MODULE_MAGIC;
 
-static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum ASN1_STRING_to_text(ASN1_STRING *str);
+static Datum X509_NAME_field_to_text(const X509_NAME *name, text *fieldName);
+static Datum ASN1_STRING_to_text(const ASN1_STRING *str);
 
 /*
  * Function context for data persisting over repeated calls.
@@ -145,7 +145,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
  * function.
  */
 static Datum
-ASN1_STRING_to_text(ASN1_STRING *str)
+ASN1_STRING_to_text(const ASN1_STRING *str)
 {
 	BIO		   *membuf;
 	size_t		size;
@@ -160,7 +160,7 @@ ASN1_STRING_to_text(ASN1_STRING *str)
 				(errcode(ERRCODE_OUT_OF_MEMORY),
 				 errmsg("could not create OpenSSL BIO structure")));
 	(void) BIO_set_close(membuf, BIO_CLOSE);
-	ASN1_STRING_print_ex(membuf, str,
+	ASN1_STRING_print_ex(membuf, unconstify(ASN1_STRING *, str),
 						 ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
 						  | ASN1_STRFLGS_UTF8_CONVERT));
 	/* ensure null termination of the BIO's content */
@@ -191,12 +191,12 @@ ASN1_STRING_to_text(ASN1_STRING *str)
  * part of name
  */
 static Datum
-X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
+X509_NAME_field_to_text(const X509_NAME *name, text *fieldName)
 {
 	char	   *string_fieldname;
 	int			nid,
 				index;
-	ASN1_STRING *data;
+	const ASN1_STRING *data;
 
 	string_fieldname = text_to_cstring(fieldName);
 	nid = OBJ_txt2nid(string_fieldname);
@@ -206,10 +206,10 @@ X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
 				 errmsg("invalid X.509 field name: \"%s\"",
 						string_fieldname)));
 	pfree(string_fieldname);
-	index = X509_NAME_get_index_by_NID(name, nid, -1);
+	index = X509_NAME_get_index_by_NID(unconstify(X509_NAME *, name), nid, -1);
 	if (index < 0)
 		return (Datum) 0;
-	data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, index));
+	data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(unconstify(X509_NAME *, name), index));
 	return ASN1_STRING_to_text(data);
 }
 
@@ -418,8 +418,8 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 		HeapTuple	tuple;
 		Datum		result;
 		BIO		   *membuf;
-		X509_EXTENSION *ext;
-		ASN1_OBJECT *obj;
+		const X509_EXTENSION *ext;
+		const ASN1_OBJECT *obj;
 		int			nid;
 		int			len;
 
@@ -432,7 +432,7 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 
 		/* Get the extension from the certificate */
 		ext = X509_get_ext(cert, call_cntr);
-		obj = X509_EXTENSION_get_object(ext);
+		obj = X509_EXTENSION_get_object(unconstify(X509_EXTENSION *, ext));
 
 		/* Get the extension name */
 		nid = OBJ_obj2nid(obj);
@@ -445,7 +445,7 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 		nulls[0] = false;
 
 		/* Get the extension value */
-		if (X509V3_EXT_print(membuf, ext, 0, 0) <= 0)
+		if (X509V3_EXT_print(membuf, unconstify(X509_EXTENSION *, ext), 0, 0) <= 0)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("could not print extension value in certificate at position %d",
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 8df8ed3c90..8fa4963ced 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -64,7 +64,7 @@ static bool initialize_dh(SSL_CTX *context, bool isServerStart);
 static bool initialize_ecdh(SSL_CTX *context, bool isServerStart);
 static const char *SSLerrmessage(unsigned long ecode);
 
-static char *X509_NAME_to_cstring(X509_NAME *name);
+static char *X509_NAME_to_cstring(const X509_NAME *name);
 
 static SSL_CTX *SSL_context = NULL;
 static bool SSL_initialized = false;
@@ -580,18 +580,18 @@ aloop:
 	if (port->peer != NULL)
 	{
 		int			len;
-		X509_NAME  *x509name = X509_get_subject_name(port->peer);
+		const X509_NAME *x509name = X509_get_subject_name(port->peer);
 		char	   *peer_dn;
 		BIO		   *bio = NULL;
 		BUF_MEM    *bio_buf = NULL;
 
-		len = X509_NAME_get_text_by_NID(x509name, NID_commonName, NULL, 0);
+		len = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, NULL, 0);
 		if (len != -1)
 		{
 			char	   *peer_cn;
 
 			peer_cn = MemoryContextAlloc(TopMemoryContext, len + 1);
-			r = X509_NAME_get_text_by_NID(x509name, NID_commonName, peer_cn,
+			r = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, peer_cn,
 										  len + 1);
 			peer_cn[len] = '\0';
 			if (r != len)
@@ -632,7 +632,7 @@ aloop:
 		 * which make regular expression matching a bit easier. Also note that
 		 * it prints the Subject fields in reverse order.
 		 */
-		X509_NAME_print_ex(bio, x509name, 0, XN_FLAG_RFC2253);
+		X509_NAME_print_ex(bio, unconstify(X509_NAME *, x509name), 0, XN_FLAG_RFC2253);
 		if (BIO_get_mem_ptr(bio, &bio_buf) <= 0)
 		{
 			BIO_free(bio);
@@ -1406,14 +1406,14 @@ be_tls_get_certificate_hash(Port *port, size_t *len)
  *
  */
 static char *
-X509_NAME_to_cstring(X509_NAME *name)
+X509_NAME_to_cstring(const X509_NAME *name)
 {
 	BIO		   *membuf = BIO_new(BIO_s_mem());
 	int			i,
 				nid,
-				count = X509_NAME_entry_count(name);
-	X509_NAME_ENTRY *e;
-	ASN1_STRING *v;
+				count = X509_NAME_entry_count(unconstify(X509_NAME *, name));
+	const X509_NAME_ENTRY *e;
+	const ASN1_STRING *v;
 	const char *field_name;
 	size_t		size;
 	char		nullterm;
@@ -1429,13 +1429,13 @@ X509_NAME_to_cstring(X509_NAME *name)
 	(void) BIO_set_close(membuf, BIO_CLOSE);
 	for (i = 0; i < count; i++)
 	{
-		e = X509_NAME_get_entry(name, i);
-		nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
+		e = X509_NAME_get_entry(unconstify(X509_NAME *, name), i);
+		nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(unconstify(X509_NAME_ENTRY *, e)));
 		if (nid == NID_undef)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("could not get NID for ASN1_OBJECT object")));
-		v = X509_NAME_ENTRY_get_data(e);
+		v = X509_NAME_ENTRY_get_data(unconstify(X509_NAME_ENTRY *, e));
 		field_name = OBJ_nid2sn(nid);
 		if (field_name == NULL)
 			field_name = OBJ_nid2ln(nid);
@@ -1444,7 +1444,7 @@ X509_NAME_to_cstring(X509_NAME *name)
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
 		BIO_printf(membuf, "/%s=", field_name);
-		ASN1_STRING_print_ex(membuf, v,
+		ASN1_STRING_print_ex(membuf, unconstify(ASN1_STRING *, v),
 							 ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
 							  | ASN1_STRFLGS_UTF8_CONVERT));
 	}
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 5f340494b7..908a3261f2 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -64,7 +64,7 @@
 
 static int	verify_cb(int ok, X509_STORE_CTX *ctx);
 static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
-															  ASN1_STRING *name,
+															  const ASN1_STRING *name,
 															  char **store_name);
 static void destroy_ssl_system(void);
 static int	initialize_SSL(PGconn *conn);
@@ -481,7 +481,8 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
  * into a plain C string.
  */
 static int
-openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
+openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
+												  const ASN1_STRING *name_entry,
 												  char **store_name)
 {
 	int			len;
@@ -570,14 +571,14 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	 */
 	if (*names_examined == 0)
 	{
-		X509_NAME  *subject_name;
+		const X509_NAME *subject_name;
 
 		subject_name = X509_get_subject_name(conn->peer);
 		if (subject_name != NULL)
 		{
 			int			cn_index;
 
-			cn_index = X509_NAME_get_index_by_NID(subject_name,
+			cn_index = X509_NAME_get_index_by_NID(unconstify(X509_NAME *, subject_name),
 												  NID_commonName, -1);
 			if (cn_index >= 0)
 			{
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index cc7bd98c83..f6b20186f1 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -538,7 +538,7 @@ $node->connect_fails(
 $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
 	"certificate authorization fails with revoked client cert",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|,
+	expected_stderr => qr!SSL error: (ssl[a-z0-9/]*|tls) alert certificate revoked!,
 	# revoked certificates should not authenticate the user
 	log_unlike => [qr/connection authenticated:/],);
 
@@ -591,7 +591,7 @@ switch_server_cert($node, 'server-cn-only', undef, undef,
 $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
 	"certificate authorization fails with revoked client cert with server-side CRL directory",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|);
+	expected_stderr => qr!SSL error: (ssl[a-z0-9/]*|tls) alert certificate revoked!);
 
 # clean up
 foreach my $key (@keys)
-- 
2.39.3 (Apple Git-146)



^ permalink  raw  reply  [nested|flat] 124+ messages in thread

* Re: PostgreSQL and OpenSSL 4.0.0
  2026-04-16 13:32 PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-04-17 21:50 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 13:44   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
@ 2026-05-07 19:32     ` Cary Huang <[email protected]>
  2026-05-07 19:39       ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2 siblings, 1 reply; 124+ messages in thread

From: Cary Huang @ 2026-05-07 19:32 UTC (permalink / raw)
  To: Daniel Gustafsson <[email protected]>; +Cc: Michael Paquier <[email protected]>; PostgreSQL-development <[email protected]>

Hi

I tried the patch and Postgres and sslinfo compiled with no warnings as
expected.

However, in OpenSSL 4.0, I noticed that it reports certificate revocation
errors differently from previous versions, causing the SSL tests to fail.
The test expects "ssl alert certificate revoked", but OpenSSL 4.0 returns
"tls alert certificate revoked" instead.

I made a patch to make the ssl tests compatible with OpenSSL 4.0 as well.

thanks!

Cary Huang
-------------
HighGo Software Inc. (Canada)
[email protected]
www.highgo.ca


Attachments:

  [application/octet-stream] 0001-fix-ssl-test-on-openssl-4.0.patch (2.8K, ../../[email protected]/2-0001-fix-ssl-test-on-openssl-4.0.patch)
  download | inline diff:
From f73b87ea3036f3cebc9aa4f16d41e07d7af1b166 Mon Sep 17 00:00:00 2001
From: Cary Huang <[email protected]>
Date: Thu, 7 May 2026 12:24:27 -0700
Subject: [PATCH] support OpenSSL 4.0.0 style revocation message:
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

OpenSSL 4.0 reports revoked certificates as “tls alert certificate revoked”
instead of “ssl... alert certificate revoked”. Update the test regex to accept
both variants to avoid false failures.
---
 src/test/ssl/t/001_ssltests.pl | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 0af887caa6..b12ad1f8df 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -885,7 +885,7 @@ $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt "
 	  . sslkey('client-revoked.key'),
 	"certificate authorization fails with revoked client cert",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|,
+	expected_stderr => qr|SSL error: (?:ssl\|tls)[a-z0-9/]* alert certificate revoked|,
 	log_like => [
 		qr{Client certificate verification failed at depth 0: certificate revoked},
 		qr{Failed certificate data \(unverified\): subject "/CN=ssltestuser", serial number \d+, issuer "/CN=Test CA for PostgreSQL SSL regression test client certs"},
@@ -987,7 +987,7 @@ $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt "
 	  . sslkey('client-revoked.key'),
 	"certificate authorization fails with revoked client cert with server-side CRL directory",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|,
+	expected_stderr => qr|SSL error: (?:ssl\|tls)[a-z0-9/]* alert certificate revoked|,
 	log_like => [
 		qr{Client certificate verification failed at depth 0: certificate revoked},
 		qr{Failed certificate data \(unverified\): subject "/CN=ssltestuser", serial number \d+, issuer "/CN=Test CA for PostgreSQL SSL regression test client certs"},
@@ -998,7 +998,7 @@ $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked-utf8.crt "
 	  . sslkey('client-revoked-utf8.key'),
 	"certificate authorization fails with revoked UTF-8 client cert with server-side CRL directory",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|,
+	expected_stderr => qr|SSL error: (?:ssl\|tls)[a-z0-9/]* alert certificate revoked|,
 	log_like => [
 		qr{Client certificate verification failed at depth 0: certificate revoked},
 		qr{Failed certificate data \(unverified\): subject "/CN=\\xce\\x9f\\xce\\xb4\\xcf\\x85\\xcf\\x83\\xcf\\x83\\xce\\xad\\xce\\xb1\\xcf\\x82", serial number \d+, issuer "/CN=Test CA for PostgreSQL SSL regression test client certs"},
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 124+ messages in thread

* Re: PostgreSQL and OpenSSL 4.0.0
  2026-04-16 13:32 PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-04-17 21:50 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 13:44   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-07 19:32     ` Re: PostgreSQL and OpenSSL 4.0.0 Cary Huang <[email protected]>
@ 2026-05-07 19:39       ` Daniel Gustafsson <[email protected]>
  0 siblings, 0 replies; 124+ messages in thread

From: Daniel Gustafsson @ 2026-05-07 19:39 UTC (permalink / raw)
  To: Cary Huang <[email protected]>; +Cc: Michael Paquier <[email protected]>; PostgreSQL-development <[email protected]>

> On 7 May 2026, at 21:32, Cary Huang <[email protected]> wrote:

> I tried the patch and Postgres and sslinfo compiled with no warnings as
> expected.

Thanks for looking!

> However, in OpenSSL 4.0, I noticed that it reports certificate revocation
> errors differently from previous versions, causing the SSL tests to fail.
> The test expects "ssl alert certificate revoked", but OpenSSL 4.0 returns
> "tls alert certificate revoked" instead.

Which version of the patch did you try?  I thought I had fixed that in the
patchset I posted earlier today but perhaps I missed some parts.

--
Daniel Gustafsson






^ permalink  raw  reply  [nested|flat] 124+ messages in thread

* Re: PostgreSQL and OpenSSL 4.0.0
  2026-04-16 13:32 PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-04-17 21:50 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 13:44   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
@ 2026-05-07 19:51     ` Daniel Gustafsson <[email protected]>
  2 siblings, 0 replies; 124+ messages in thread

From: Daniel Gustafsson @ 2026-05-07 19:51 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: PostgreSQL-development <[email protected]>

> On 7 May 2026, at 15:44, Daniel Gustafsson <[email protected]> wrote:

> For 14 through master the attached compiles without warnings and tests green on
> all the supported versions of OpenSSL and LibreSSL.

On the topic of supported OpenSSL versions.

REL_14-REL_16 supports 1.0.1 (eol 9 years ago), REL_17 bumps that to 1.0.2 (eol
6 years ago) and starting with REL_18 we require 1.1.1 (eol ~2.5 years ago).
By the time we ship REL_19, 3.4 has a few weeks left in support and before 19.1
ships, 3.5 will be the oldest non-EOL version of OpenSSL.

--
Daniel Gustafsson






^ permalink  raw  reply  [nested|flat] 124+ messages in thread

* Re: PostgreSQL and OpenSSL 4.0.0
  2026-04-16 13:32 PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-04-17 21:50 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 13:44   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
@ 2026-05-07 22:13     ` Michael Paquier <[email protected]>
  2026-05-07 22:22       ` Re: PostgreSQL and OpenSSL 4.0.0 Tom Lane <[email protected]>
  2 siblings, 1 reply; 124+ messages in thread

From: Michael Paquier @ 2026-05-07 22:13 UTC (permalink / raw)
  To: Daniel Gustafsson <[email protected]>; +Cc: PostgreSQL-development <[email protected]>

On Thu, May 07, 2026 at 03:44:45PM +0200, Daniel Gustafsson wrote:
> For 14 through master the attached compiles without warnings and tests green on
> all the supported versions of OpenSSL and LibreSSL.  That being said, I'm not
> sure that we want to go all the way to 14 since if something does break, we
> can't really go around fixing it - I think amending the docs in 14 stating that
> OpenSSL 3.6 is the highest supported version is a better solution.

One issue with this approach is that any builds on these branches (say
REL_14_STABLE + OpenSSL 1.0.1) would be forced to either upgrade
OpenSSL to at least 3.6 for a minor Postgres update or give up on any
fix we can put on the 14 stable branch for six more months.  None of
these solutions are cool.
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 124+ messages in thread

* Re: PostgreSQL and OpenSSL 4.0.0
  2026-04-16 13:32 PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-04-17 21:50 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 13:44   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-07 22:13     ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
@ 2026-05-07 22:22       ` Tom Lane <[email protected]>
  2026-05-07 22:54         ` Re: PostgreSQL and OpenSSL 4.0.0 Cary Huang <[email protected]>
  2026-05-08 07:07         ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  0 siblings, 2 replies; 124+ messages in thread

From: Tom Lane @ 2026-05-07 22:22 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Daniel Gustafsson <[email protected]>; PostgreSQL-development <[email protected]>

Michael Paquier <[email protected]> writes:
> On Thu, May 07, 2026 at 03:44:45PM +0200, Daniel Gustafsson wrote:
>> For 14 through master the attached compiles without warnings and tests green on
>> all the supported versions of OpenSSL and LibreSSL.  That being said, I'm not
>> sure that we want to go all the way to 14 since if something does break, we
>> can't really go around fixing it - I think amending the docs in 14 stating that
>> OpenSSL 3.6 is the highest supported version is a better solution.

> One issue with this approach is that any builds on these branches (say
> REL_14_STABLE + OpenSSL 1.0.1) would be forced to either upgrade
> OpenSSL to at least 3.6 for a minor Postgres update or give up on any
> fix we can put on the 14 stable branch for six more months.  None of
> these solutions are cool.

With one eye on the calendar, I think the right way to proceed is to
push this to all branches (including 14) soon after next week's
releases.  I feel this is too high-risk to shove in just before a
release, but shortly after one is ideal since we'll have 3 months to
find out any problems.

I would support omitting 14 if we were down to just one remaining
release for it, but we'll have 2 (August and November).  So there
will still be an opportunity to fix things if there's an issue
that manages to escape notice until after the August releases.

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 124+ messages in thread

* Re: PostgreSQL and OpenSSL 4.0.0
  2026-04-16 13:32 PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-04-17 21:50 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 13:44   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-07 22:13     ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 22:22       ` Re: PostgreSQL and OpenSSL 4.0.0 Tom Lane <[email protected]>
@ 2026-05-07 22:54         ` Cary Huang <[email protected]>
  1 sibling, 0 replies; 124+ messages in thread

From: Cary Huang @ 2026-05-07 22:54 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Michael Paquier <[email protected]>; Daniel Gustafsson <[email protected]>; PostgreSQL-development <[email protected]>

Hi

> Which version of the patch did you try? I thought I had fixed that in the
> patchset I posted earlier today but perhaps I missed some parts.

I tried the very first patch you shared. I see that the ssl test errors have
been addressed by the patches you shared after. So it is all good. I'll try
testing other test cases that may have used OpenSSL APIs.

thanks!

Cary Huang
-------------
HighGo Software Inc. (Canada)
[email protected]
www.highgo.ca









^ permalink  raw  reply  [nested|flat] 124+ messages in thread

* Re: PostgreSQL and OpenSSL 4.0.0
  2026-04-16 13:32 PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-04-17 21:50 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 13:44   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-07 22:13     ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 22:22       ` Re: PostgreSQL and OpenSSL 4.0.0 Tom Lane <[email protected]>
@ 2026-05-08 07:07         ` Daniel Gustafsson <[email protected]>
  2026-05-08 07:17           ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  1 sibling, 1 reply; 124+ messages in thread

From: Daniel Gustafsson @ 2026-05-08 07:07 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Michael Paquier <[email protected]>; PostgreSQL-development <[email protected]>

> On 8 May 2026, at 00:22, Tom Lane <[email protected]> wrote:
> 
> Michael Paquier <[email protected]> writes:
>> On Thu, May 07, 2026 at 03:44:45PM +0200, Daniel Gustafsson wrote:
>>> For 14 through master the attached compiles without warnings and tests green on
>>> all the supported versions of OpenSSL and LibreSSL.  That being said, I'm not
>>> sure that we want to go all the way to 14 since if something does break, we
>>> can't really go around fixing it - I think amending the docs in 14 stating that
>>> OpenSSL 3.6 is the highest supported version is a better solution.
> 
>> One issue with this approach is that any builds on these branches (say
>> REL_14_STABLE + OpenSSL 1.0.1) would be forced to either upgrade
>> OpenSSL to at least 3.6 for a minor Postgres update or give up on any
>> fix we can put on the 14 stable branch for six more months.  None of
>> these solutions are cool.

Not sure I follow, anyone still building with a X years out of support OpenSSL
will most likely keep doing so regardless of what CVE's are published.  It
could of course make backpatching trickier if thats what you mean?

> With one eye on the calendar, I think the right way to proceed is to
> push this to all branches (including 14) soon after next week's
> releases.  I feel this is too high-risk to shove in just before a
> release, but shortly after one is ideal since we'll have 3 months to
> find out any problems.
> 
> I would support omitting 14 if we were down to just one remaining
> release for it, but we'll have 2 (August and November).  So there
> will still be an opportunity to fix things if there's an issue
> that manages to escape notice until after the August releases.

Doh..  thanks.  I was off-by-one and convinced myself we only have one more
minor on 14.  With two more scheduled I agree that we should go for OpenSSL 4
support in 14 as well.  I'll re-test and prep all the branches with all the
version of OpenSSL so that I can get this in shortly after the next weeks
releases go out.

--
Daniel Gustafsson






^ permalink  raw  reply  [nested|flat] 124+ messages in thread

* Re: PostgreSQL and OpenSSL 4.0.0
  2026-04-16 13:32 PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-04-17 21:50 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 13:44   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-07 22:13     ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 22:22       ` Re: PostgreSQL and OpenSSL 4.0.0 Tom Lane <[email protected]>
  2026-05-08 07:07         ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
@ 2026-05-08 07:17           ` Michael Paquier <[email protected]>
  2026-05-08 07:21             ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  0 siblings, 1 reply; 124+ messages in thread

From: Michael Paquier @ 2026-05-08 07:17 UTC (permalink / raw)
  To: Daniel Gustafsson <[email protected]>; +Cc: Tom Lane <[email protected]>; PostgreSQL-development <[email protected]>

On Fri, May 08, 2026 at 09:07:41AM +0200, Daniel Gustafsson wrote:
> Not sure I follow, anyone still building with a X years out of support OpenSSL
> will most likely keep doing so regardless of what CVE's are published.  It
> could of course make backpatching trickier if thats what you mean?

Argh.  I've misread you here, reading a "lowest" rather than
"highest".   Documenting that 3.6 is the highest version support on 
14-stable would also work here.  My apologies for the confusion.

If the patches for REL_14_STABLE to add support for 4.0 prove to be
low-risk while messing with 1.0.1, that would the best course of
action, of course.
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 124+ messages in thread

* Re: PostgreSQL and OpenSSL 4.0.0
  2026-04-16 13:32 PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-04-17 21:50 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 13:44   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-07 22:13     ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 22:22       ` Re: PostgreSQL and OpenSSL 4.0.0 Tom Lane <[email protected]>
  2026-05-08 07:07         ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-08 07:17           ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
@ 2026-05-08 07:21             ` Daniel Gustafsson <[email protected]>
  2026-05-19 21:18               ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  0 siblings, 1 reply; 124+ messages in thread

From: Daniel Gustafsson @ 2026-05-08 07:21 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Tom Lane <[email protected]>; PostgreSQL-development <[email protected]>

> On 8 May 2026, at 09:17, Michael Paquier <[email protected]> wrote:
> 
> On Fri, May 08, 2026 at 09:07:41AM +0200, Daniel Gustafsson wrote:
>> Not sure I follow, anyone still building with a X years out of support OpenSSL
>> will most likely keep doing so regardless of what CVE's are published.  It
>> could of course make backpatching trickier if thats what you mean?
> 
> Argh.  I've misread you here, reading a "lowest" rather than
> "highest".   Documenting that 3.6 is the highest version support on 
> 14-stable would also work here.  My apologies for the confusion.

Ah, now it makes more sense =)

> If the patches for REL_14_STABLE to add support for 4.0 prove to be
> low-risk while messing with 1.0.1, that would the best course of
> action, of course.

I think the changes are straightforward enough that we can go ahead with them.
I'll re-test and re-post a new patchset for all branches once the minors ship.

--
Daniel Gustafsson







^ permalink  raw  reply  [nested|flat] 124+ messages in thread

* Re: PostgreSQL and OpenSSL 4.0.0
  2026-04-16 13:32 PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-04-17 21:50 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 13:44   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-07 22:13     ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 22:22       ` Re: PostgreSQL and OpenSSL 4.0.0 Tom Lane <[email protected]>
  2026-05-08 07:07         ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-08 07:17           ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-08 07:21             ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
@ 2026-05-19 21:18               ` Daniel Gustafsson <[email protected]>
  2026-05-26 06:02                 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  0 siblings, 1 reply; 124+ messages in thread

From: Daniel Gustafsson @ 2026-05-19 21:18 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Tom Lane <[email protected]>; PostgreSQL-development <[email protected]>

> On 8 May 2026, at 00:21, Daniel Gustafsson <[email protected]> wrote:

> I think the changes are straightforward enough that we can go ahead with them.
> I'll re-test and re-post a new patchset for all branches once the minors ship.

Attached are rebased versions of this patchset for v14-master.

--
Daniel Gustafsson



Attachments:

  [application/octet-stream] v2-REL_14-0001-Support-OpenSSL-4.patch (10.0K, ../../[email protected]/2-v2-REL_14-0001-Support-OpenSSL-4.patch)
  download | inline diff:
From 32ac8003bec3ac9d7933ac105b93a4e2e985a3a7 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Thu, 7 May 2026 10:46:18 +0200
Subject: [PATCH v2-REL_14] Support OpenSSL 4

OpenSSL 4.0.0 changed some parameters and returnvalues to const, so
we need to update our declarations and subsequently cast away const-
ness from a few callsites to make libpq build without warnings. This
is tested with OpenSSL 1.1.1 through 4.0.0 as well as with LibreSSL.

There is also an errormessage change in OpenSSL 4.0.0 which needs to
be covered by our testharness.

Author: Daniel Gustafsson <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
 contrib/sslinfo/sslinfo.c                | 24 +++++++++++-----------
 src/backend/libpq/be-secure-openssl.c    | 26 ++++++++++++------------
 src/interfaces/libpq/fe-secure-openssl.c |  9 ++++----
 src/test/ssl/t/001_ssltests.pl           |  4 ++--
 4 files changed, 32 insertions(+), 31 deletions(-)

diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..4f8a118bc9 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -21,8 +21,8 @@
 
 PG_MODULE_MAGIC;
 
-static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum ASN1_STRING_to_text(ASN1_STRING *str);
+static Datum X509_NAME_field_to_text(const X509_NAME *name, text *fieldName);
+static Datum ASN1_STRING_to_text(const ASN1_STRING *str);
 
 /*
  * Function context for data persisting over repeated calls.
@@ -145,7 +145,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
  * function.
  */
 static Datum
-ASN1_STRING_to_text(ASN1_STRING *str)
+ASN1_STRING_to_text(const ASN1_STRING *str)
 {
 	BIO		   *membuf;
 	size_t		size;
@@ -160,7 +160,7 @@ ASN1_STRING_to_text(ASN1_STRING *str)
 				(errcode(ERRCODE_OUT_OF_MEMORY),
 				 errmsg("could not create OpenSSL BIO structure")));
 	(void) BIO_set_close(membuf, BIO_CLOSE);
-	ASN1_STRING_print_ex(membuf, str,
+	ASN1_STRING_print_ex(membuf, unconstify(ASN1_STRING *, str),
 						 ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
 						  | ASN1_STRFLGS_UTF8_CONVERT));
 	/* ensure null termination of the BIO's content */
@@ -191,12 +191,12 @@ ASN1_STRING_to_text(ASN1_STRING *str)
  * part of name
  */
 static Datum
-X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
+X509_NAME_field_to_text(const X509_NAME *name, text *fieldName)
 {
 	char	   *string_fieldname;
 	int			nid,
 				index;
-	ASN1_STRING *data;
+	const ASN1_STRING *data;
 
 	string_fieldname = text_to_cstring(fieldName);
 	nid = OBJ_txt2nid(string_fieldname);
@@ -206,10 +206,10 @@ X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
 				 errmsg("invalid X.509 field name: \"%s\"",
 						string_fieldname)));
 	pfree(string_fieldname);
-	index = X509_NAME_get_index_by_NID(name, nid, -1);
+	index = X509_NAME_get_index_by_NID(unconstify(X509_NAME *, name), nid, -1);
 	if (index < 0)
 		return (Datum) 0;
-	data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, index));
+	data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(unconstify(X509_NAME *, name), index));
 	return ASN1_STRING_to_text(data);
 }
 
@@ -418,8 +418,8 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 		HeapTuple	tuple;
 		Datum		result;
 		BIO		   *membuf;
-		X509_EXTENSION *ext;
-		ASN1_OBJECT *obj;
+		const X509_EXTENSION *ext;
+		const ASN1_OBJECT *obj;
 		int			nid;
 		int			len;
 
@@ -432,7 +432,7 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 
 		/* Get the extension from the certificate */
 		ext = X509_get_ext(cert, call_cntr);
-		obj = X509_EXTENSION_get_object(ext);
+		obj = X509_EXTENSION_get_object(unconstify(X509_EXTENSION *, ext));
 
 		/* Get the extension name */
 		nid = OBJ_obj2nid(obj);
@@ -445,7 +445,7 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 		nulls[0] = false;
 
 		/* Get the extension value */
-		if (X509V3_EXT_print(membuf, ext, 0, 0) <= 0)
+		if (X509V3_EXT_print(membuf, unconstify(X509_EXTENSION *, ext), 0, 0) <= 0)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("could not print extension value in certificate at position %d",
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 8df8ed3c90..8fa4963ced 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -64,7 +64,7 @@ static bool initialize_dh(SSL_CTX *context, bool isServerStart);
 static bool initialize_ecdh(SSL_CTX *context, bool isServerStart);
 static const char *SSLerrmessage(unsigned long ecode);
 
-static char *X509_NAME_to_cstring(X509_NAME *name);
+static char *X509_NAME_to_cstring(const X509_NAME *name);
 
 static SSL_CTX *SSL_context = NULL;
 static bool SSL_initialized = false;
@@ -580,18 +580,18 @@ aloop:
 	if (port->peer != NULL)
 	{
 		int			len;
-		X509_NAME  *x509name = X509_get_subject_name(port->peer);
+		const X509_NAME *x509name = X509_get_subject_name(port->peer);
 		char	   *peer_dn;
 		BIO		   *bio = NULL;
 		BUF_MEM    *bio_buf = NULL;
 
-		len = X509_NAME_get_text_by_NID(x509name, NID_commonName, NULL, 0);
+		len = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, NULL, 0);
 		if (len != -1)
 		{
 			char	   *peer_cn;
 
 			peer_cn = MemoryContextAlloc(TopMemoryContext, len + 1);
-			r = X509_NAME_get_text_by_NID(x509name, NID_commonName, peer_cn,
+			r = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, peer_cn,
 										  len + 1);
 			peer_cn[len] = '\0';
 			if (r != len)
@@ -632,7 +632,7 @@ aloop:
 		 * which make regular expression matching a bit easier. Also note that
 		 * it prints the Subject fields in reverse order.
 		 */
-		X509_NAME_print_ex(bio, x509name, 0, XN_FLAG_RFC2253);
+		X509_NAME_print_ex(bio, unconstify(X509_NAME *, x509name), 0, XN_FLAG_RFC2253);
 		if (BIO_get_mem_ptr(bio, &bio_buf) <= 0)
 		{
 			BIO_free(bio);
@@ -1406,14 +1406,14 @@ be_tls_get_certificate_hash(Port *port, size_t *len)
  *
  */
 static char *
-X509_NAME_to_cstring(X509_NAME *name)
+X509_NAME_to_cstring(const X509_NAME *name)
 {
 	BIO		   *membuf = BIO_new(BIO_s_mem());
 	int			i,
 				nid,
-				count = X509_NAME_entry_count(name);
-	X509_NAME_ENTRY *e;
-	ASN1_STRING *v;
+				count = X509_NAME_entry_count(unconstify(X509_NAME *, name));
+	const X509_NAME_ENTRY *e;
+	const ASN1_STRING *v;
 	const char *field_name;
 	size_t		size;
 	char		nullterm;
@@ -1429,13 +1429,13 @@ X509_NAME_to_cstring(X509_NAME *name)
 	(void) BIO_set_close(membuf, BIO_CLOSE);
 	for (i = 0; i < count; i++)
 	{
-		e = X509_NAME_get_entry(name, i);
-		nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
+		e = X509_NAME_get_entry(unconstify(X509_NAME *, name), i);
+		nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(unconstify(X509_NAME_ENTRY *, e)));
 		if (nid == NID_undef)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("could not get NID for ASN1_OBJECT object")));
-		v = X509_NAME_ENTRY_get_data(e);
+		v = X509_NAME_ENTRY_get_data(unconstify(X509_NAME_ENTRY *, e));
 		field_name = OBJ_nid2sn(nid);
 		if (field_name == NULL)
 			field_name = OBJ_nid2ln(nid);
@@ -1444,7 +1444,7 @@ X509_NAME_to_cstring(X509_NAME *name)
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
 		BIO_printf(membuf, "/%s=", field_name);
-		ASN1_STRING_print_ex(membuf, v,
+		ASN1_STRING_print_ex(membuf, unconstify(ASN1_STRING *, v),
 							 ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
 							  | ASN1_STRFLGS_UTF8_CONVERT));
 	}
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 5f340494b7..908a3261f2 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -64,7 +64,7 @@
 
 static int	verify_cb(int ok, X509_STORE_CTX *ctx);
 static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
-															  ASN1_STRING *name,
+															  const ASN1_STRING *name,
 															  char **store_name);
 static void destroy_ssl_system(void);
 static int	initialize_SSL(PGconn *conn);
@@ -481,7 +481,8 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
  * into a plain C string.
  */
 static int
-openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
+openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
+												  const ASN1_STRING *name_entry,
 												  char **store_name)
 {
 	int			len;
@@ -570,14 +571,14 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	 */
 	if (*names_examined == 0)
 	{
-		X509_NAME  *subject_name;
+		const X509_NAME *subject_name;
 
 		subject_name = X509_get_subject_name(conn->peer);
 		if (subject_name != NULL)
 		{
 			int			cn_index;
 
-			cn_index = X509_NAME_get_index_by_NID(subject_name,
+			cn_index = X509_NAME_get_index_by_NID(unconstify(X509_NAME *, subject_name),
 												  NID_commonName, -1);
 			if (cn_index >= 0)
 			{
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index cc7bd98c83..f6b20186f1 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -538,7 +538,7 @@ $node->connect_fails(
 $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
 	"certificate authorization fails with revoked client cert",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|,
+	expected_stderr => qr!SSL error: (ssl[a-z0-9/]*|tls) alert certificate revoked!,
 	# revoked certificates should not authenticate the user
 	log_unlike => [qr/connection authenticated:/],);
 
@@ -591,7 +591,7 @@ switch_server_cert($node, 'server-cn-only', undef, undef,
 $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
 	"certificate authorization fails with revoked client cert with server-side CRL directory",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|);
+	expected_stderr => qr!SSL error: (ssl[a-z0-9/]*|tls) alert certificate revoked!);
 
 # clean up
 foreach my $key (@keys)
-- 
2.39.3 (Apple Git-146)



  [application/octet-stream] v2-REL_15-0001-Support-OpenSSL-4.patch (9.9K, ../../[email protected]/3-v2-REL_15-0001-Support-OpenSSL-4.patch)
  download | inline diff:
From b8bb88435278d93f21fd537a010de4cd1bb08752 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Thu, 7 May 2026 10:46:18 +0200
Subject: [PATCH v2-REL_15] Support OpenSSL 4

OpenSSL 4.0.0 changed some parameters and returnvalues to const, so
we need to update our declarations and subsequently cast away const-
ness from a few callsites to make libpq build without warnings. This
is tested with OpenSSL 1.1.1 through 4.0.0 as well as with LibreSSL.

There is also an errormessage change in OpenSSL 4.0.0 which needs to
be covered by our testharness.

Author: Daniel Gustafsson <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
 contrib/sslinfo/sslinfo.c                | 24 +++++++++++-----------
 src/backend/libpq/be-secure-openssl.c    | 26 ++++++++++++------------
 src/interfaces/libpq/fe-secure-openssl.c |  9 ++++----
 src/test/ssl/t/001_ssltests.pl           |  4 ++--
 4 files changed, 32 insertions(+), 31 deletions(-)

diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5fd46b98741..4251ccfd174 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -32,8 +32,8 @@
 
 PG_MODULE_MAGIC;
 
-static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum ASN1_STRING_to_text(ASN1_STRING *str);
+static Datum X509_NAME_field_to_text(const X509_NAME *name, text *fieldName);
+static Datum ASN1_STRING_to_text(const ASN1_STRING *str);
 
 /*
  * Function context for data persisting over repeated calls.
@@ -156,7 +156,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
  * function.
  */
 static Datum
-ASN1_STRING_to_text(ASN1_STRING *str)
+ASN1_STRING_to_text(const ASN1_STRING *str)
 {
 	BIO		   *membuf;
 	size_t		size;
@@ -171,7 +171,7 @@ ASN1_STRING_to_text(ASN1_STRING *str)
 				(errcode(ERRCODE_OUT_OF_MEMORY),
 				 errmsg("could not create OpenSSL BIO structure")));
 	(void) BIO_set_close(membuf, BIO_CLOSE);
-	ASN1_STRING_print_ex(membuf, str,
+	ASN1_STRING_print_ex(membuf, unconstify(ASN1_STRING *, str),
 						 ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
 						  | ASN1_STRFLGS_UTF8_CONVERT));
 	/* ensure null termination of the BIO's content */
@@ -202,12 +202,12 @@ ASN1_STRING_to_text(ASN1_STRING *str)
  * part of name
  */
 static Datum
-X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
+X509_NAME_field_to_text(const X509_NAME *name, text *fieldName)
 {
 	char	   *string_fieldname;
 	int			nid,
 				index;
-	ASN1_STRING *data;
+	const ASN1_STRING *data;
 
 	string_fieldname = text_to_cstring(fieldName);
 	nid = OBJ_txt2nid(string_fieldname);
@@ -217,10 +217,10 @@ X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
 				 errmsg("invalid X.509 field name: \"%s\"",
 						string_fieldname)));
 	pfree(string_fieldname);
-	index = X509_NAME_get_index_by_NID(name, nid, -1);
+	index = X509_NAME_get_index_by_NID(unconstify(X509_NAME *, name), nid, -1);
 	if (index < 0)
 		return (Datum) 0;
-	data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, index));
+	data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(unconstify(X509_NAME *, name), index));
 	return ASN1_STRING_to_text(data);
 }
 
@@ -429,8 +429,8 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 		HeapTuple	tuple;
 		Datum		result;
 		BIO		   *membuf;
-		X509_EXTENSION *ext;
-		ASN1_OBJECT *obj;
+		const X509_EXTENSION *ext;
+		const ASN1_OBJECT *obj;
 		int			nid;
 		int			len;
 
@@ -443,7 +443,7 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 
 		/* Get the extension from the certificate */
 		ext = X509_get_ext(cert, call_cntr);
-		obj = X509_EXTENSION_get_object(ext);
+		obj = X509_EXTENSION_get_object(unconstify(X509_EXTENSION *, ext));
 
 		/* Get the extension name */
 		nid = OBJ_obj2nid(obj);
@@ -456,7 +456,7 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 		nulls[0] = false;
 
 		/* Get the extension value */
-		if (X509V3_EXT_print(membuf, ext, 0, 0) <= 0)
+		if (X509V3_EXT_print(membuf, unconstify(X509_EXTENSION *, ext), 0, 0) <= 0)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("could not print extension value in certificate at position %d",
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index b0492c443ec..96e60a97f04 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -71,7 +71,7 @@ static bool initialize_dh(SSL_CTX *context, bool isServerStart);
 static bool initialize_ecdh(SSL_CTX *context, bool isServerStart);
 static const char *SSLerrmessage(unsigned long ecode);
 
-static char *X509_NAME_to_cstring(X509_NAME *name);
+static char *X509_NAME_to_cstring(const X509_NAME *name);
 
 static SSL_CTX *SSL_context = NULL;
 static bool SSL_initialized = false;
@@ -587,18 +587,18 @@ aloop:
 	if (port->peer != NULL)
 	{
 		int			len;
-		X509_NAME  *x509name = X509_get_subject_name(port->peer);
+		const X509_NAME *x509name = X509_get_subject_name(port->peer);
 		char	   *peer_dn;
 		BIO		   *bio = NULL;
 		BUF_MEM    *bio_buf = NULL;
 
-		len = X509_NAME_get_text_by_NID(x509name, NID_commonName, NULL, 0);
+		len = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, NULL, 0);
 		if (len != -1)
 		{
 			char	   *peer_cn;
 
 			peer_cn = MemoryContextAlloc(TopMemoryContext, len + 1);
-			r = X509_NAME_get_text_by_NID(x509name, NID_commonName, peer_cn,
+			r = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, peer_cn,
 										  len + 1);
 			peer_cn[len] = '\0';
 			if (r != len)
@@ -642,7 +642,7 @@ aloop:
 		 * which make regular expression matching a bit easier. Also note that
 		 * it prints the Subject fields in reverse order.
 		 */
-		if (X509_NAME_print_ex(bio, x509name, 0, XN_FLAG_RFC2253) == -1 ||
+		if (X509_NAME_print_ex(bio, unconstify(X509_NAME *, x509name), 0, XN_FLAG_RFC2253) == -1 ||
 			BIO_get_mem_ptr(bio, &bio_buf) <= 0)
 		{
 			BIO_free(bio);
@@ -1422,14 +1422,14 @@ be_tls_get_certificate_hash(Port *port, size_t *len)
  *
  */
 static char *
-X509_NAME_to_cstring(X509_NAME *name)
+X509_NAME_to_cstring(const X509_NAME *name)
 {
 	BIO		   *membuf = BIO_new(BIO_s_mem());
 	int			i,
 				nid,
-				count = X509_NAME_entry_count(name);
-	X509_NAME_ENTRY *e;
-	ASN1_STRING *v;
+				count = X509_NAME_entry_count(unconstify(X509_NAME *, name));
+	const X509_NAME_ENTRY *e;
+	const ASN1_STRING *v;
 	const char *field_name;
 	size_t		size;
 	char		nullterm;
@@ -1445,13 +1445,13 @@ X509_NAME_to_cstring(X509_NAME *name)
 	(void) BIO_set_close(membuf, BIO_CLOSE);
 	for (i = 0; i < count; i++)
 	{
-		e = X509_NAME_get_entry(name, i);
-		nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
+		e = X509_NAME_get_entry(unconstify(X509_NAME *, name), i);
+		nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(unconstify(X509_NAME_ENTRY *, e)));
 		if (nid == NID_undef)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("could not get NID for ASN1_OBJECT object")));
-		v = X509_NAME_ENTRY_get_data(e);
+		v = X509_NAME_ENTRY_get_data(unconstify(X509_NAME_ENTRY *, e));
 		field_name = OBJ_nid2sn(nid);
 		if (field_name == NULL)
 			field_name = OBJ_nid2ln(nid);
@@ -1460,7 +1460,7 @@ X509_NAME_to_cstring(X509_NAME *name)
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
 		BIO_printf(membuf, "/%s=", field_name);
-		ASN1_STRING_print_ex(membuf, v,
+		ASN1_STRING_print_ex(membuf, unconstify(ASN1_STRING *, v),
 							 ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
 							  | ASN1_STRFLGS_UTF8_CONVERT));
 	}
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 50d14eac0ee..d22b5279b12 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -70,7 +70,7 @@
 
 static int	verify_cb(int ok, X509_STORE_CTX *ctx);
 static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
-															  ASN1_STRING *name,
+															  const ASN1_STRING *name,
 															  char **store_name);
 static int	openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
 															ASN1_OCTET_STRING *addr_entry,
@@ -490,7 +490,8 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
  * into a plain C string.
  */
 static int
-openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
+openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
+												  const ASN1_STRING *name_entry,
 												  char **store_name)
 {
 	int			len;
@@ -683,14 +684,14 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	 */
 	if (check_cn)
 	{
-		X509_NAME  *subject_name;
+		const X509_NAME *subject_name;
 
 		subject_name = X509_get_subject_name(conn->peer);
 		if (subject_name != NULL)
 		{
 			int			cn_index;
 
-			cn_index = X509_NAME_get_index_by_NID(subject_name,
+			cn_index = X509_NAME_get_index_by_NID(unconstify(X509_NAME *, subject_name),
 												  NID_commonName, -1);
 			if (cn_index >= 0)
 			{
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index c570b48a1bd..756745b7bec 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -682,7 +682,7 @@ $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt "
 	  . sslkey('client-revoked.key'),
 	"certificate authorization fails with revoked client cert",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|,
+	expected_stderr => qr!SSL error: (ssl[a-z0-9/]*|tls) alert certificate revoked!,
 	# revoked certificates should not authenticate the user
 	log_unlike => [qr/connection authenticated:/],);
 
@@ -743,6 +743,6 @@ $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt "
 	  . sslkey('client-revoked.key'),
 	"certificate authorization fails with revoked client cert with server-side CRL directory",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|);
+	expected_stderr => qr!SSL error: (ssl[a-z0-9/]*|tls) alert certificate revoked!);
 
 done_testing();
-- 
2.39.3 (Apple Git-146)



  [application/octet-stream] v2-REL_16-0001-Support-OpenSSL-4.patch (10.9K, ../../[email protected]/4-v2-REL_16-0001-Support-OpenSSL-4.patch)
  download | inline diff:
From 3365822f753d2ad66008d2388da2a085efd87cd0 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Thu, 7 May 2026 10:46:18 +0200
Subject: [PATCH v2-REL_16] Support OpenSSL 4

OpenSSL 4.0.0 changed some parameters and returnvalues to const, so
we need to update our declarations and subsequently cast away const-
ness from a few callsites to make libpq build without warnings. This
is tested with OpenSSL 1.1.1 through 4.0.0 as well as with LibreSSL.

There is also an errormessage change in OpenSSL 4.0.0 which needs to
be covered by our testharness.

Author: Daniel Gustafsson <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
 contrib/sslinfo/sslinfo.c                | 24 +++++++++++-----------
 src/backend/libpq/be-secure-openssl.c    | 26 ++++++++++++------------
 src/interfaces/libpq/fe-secure-openssl.c | 13 ++++++------
 src/test/ssl/t/001_ssltests.pl           |  4 ++--
 4 files changed, 34 insertions(+), 33 deletions(-)

diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5fd46b98741..4251ccfd174 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -32,8 +32,8 @@
 
 PG_MODULE_MAGIC;
 
-static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum ASN1_STRING_to_text(ASN1_STRING *str);
+static Datum X509_NAME_field_to_text(const X509_NAME *name, text *fieldName);
+static Datum ASN1_STRING_to_text(const ASN1_STRING *str);
 
 /*
  * Function context for data persisting over repeated calls.
@@ -156,7 +156,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
  * function.
  */
 static Datum
-ASN1_STRING_to_text(ASN1_STRING *str)
+ASN1_STRING_to_text(const ASN1_STRING *str)
 {
 	BIO		   *membuf;
 	size_t		size;
@@ -171,7 +171,7 @@ ASN1_STRING_to_text(ASN1_STRING *str)
 				(errcode(ERRCODE_OUT_OF_MEMORY),
 				 errmsg("could not create OpenSSL BIO structure")));
 	(void) BIO_set_close(membuf, BIO_CLOSE);
-	ASN1_STRING_print_ex(membuf, str,
+	ASN1_STRING_print_ex(membuf, unconstify(ASN1_STRING *, str),
 						 ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
 						  | ASN1_STRFLGS_UTF8_CONVERT));
 	/* ensure null termination of the BIO's content */
@@ -202,12 +202,12 @@ ASN1_STRING_to_text(ASN1_STRING *str)
  * part of name
  */
 static Datum
-X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
+X509_NAME_field_to_text(const X509_NAME *name, text *fieldName)
 {
 	char	   *string_fieldname;
 	int			nid,
 				index;
-	ASN1_STRING *data;
+	const ASN1_STRING *data;
 
 	string_fieldname = text_to_cstring(fieldName);
 	nid = OBJ_txt2nid(string_fieldname);
@@ -217,10 +217,10 @@ X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
 				 errmsg("invalid X.509 field name: \"%s\"",
 						string_fieldname)));
 	pfree(string_fieldname);
-	index = X509_NAME_get_index_by_NID(name, nid, -1);
+	index = X509_NAME_get_index_by_NID(unconstify(X509_NAME *, name), nid, -1);
 	if (index < 0)
 		return (Datum) 0;
-	data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, index));
+	data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(unconstify(X509_NAME *, name), index));
 	return ASN1_STRING_to_text(data);
 }
 
@@ -429,8 +429,8 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 		HeapTuple	tuple;
 		Datum		result;
 		BIO		   *membuf;
-		X509_EXTENSION *ext;
-		ASN1_OBJECT *obj;
+		const X509_EXTENSION *ext;
+		const ASN1_OBJECT *obj;
 		int			nid;
 		int			len;
 
@@ -443,7 +443,7 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 
 		/* Get the extension from the certificate */
 		ext = X509_get_ext(cert, call_cntr);
-		obj = X509_EXTENSION_get_object(ext);
+		obj = X509_EXTENSION_get_object(unconstify(X509_EXTENSION *, ext));
 
 		/* Get the extension name */
 		nid = OBJ_obj2nid(obj);
@@ -456,7 +456,7 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 		nulls[0] = false;
 
 		/* Get the extension value */
-		if (X509V3_EXT_print(membuf, ext, 0, 0) <= 0)
+		if (X509V3_EXT_print(membuf, unconstify(X509_EXTENSION *, ext), 0, 0) <= 0)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("could not print extension value in certificate at position %d",
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 49803b37416..e54aaf57b2e 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -71,7 +71,7 @@ static bool initialize_dh(SSL_CTX *context, bool isServerStart);
 static bool initialize_ecdh(SSL_CTX *context, bool isServerStart);
 static const char *SSLerrmessage(unsigned long ecode);
 
-static char *X509_NAME_to_cstring(X509_NAME *name);
+static char *X509_NAME_to_cstring(const X509_NAME *name);
 
 static SSL_CTX *SSL_context = NULL;
 static bool SSL_initialized = false;
@@ -592,18 +592,18 @@ aloop:
 	if (port->peer != NULL)
 	{
 		int			len;
-		X509_NAME  *x509name = X509_get_subject_name(port->peer);
+		const X509_NAME *x509name = X509_get_subject_name(port->peer);
 		char	   *peer_dn;
 		BIO		   *bio = NULL;
 		BUF_MEM    *bio_buf = NULL;
 
-		len = X509_NAME_get_text_by_NID(x509name, NID_commonName, NULL, 0);
+		len = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, NULL, 0);
 		if (len != -1)
 		{
 			char	   *peer_cn;
 
 			peer_cn = MemoryContextAlloc(TopMemoryContext, len + 1);
-			r = X509_NAME_get_text_by_NID(x509name, NID_commonName, peer_cn,
+			r = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, peer_cn,
 										  len + 1);
 			peer_cn[len] = '\0';
 			if (r != len)
@@ -647,7 +647,7 @@ aloop:
 		 * which make regular expression matching a bit easier. Also note that
 		 * it prints the Subject fields in reverse order.
 		 */
-		if (X509_NAME_print_ex(bio, x509name, 0, XN_FLAG_RFC2253) == -1 ||
+		if (X509_NAME_print_ex(bio, unconstify(X509_NAME *, x509name), 0, XN_FLAG_RFC2253) == -1 ||
 			BIO_get_mem_ptr(bio, &bio_buf) <= 0)
 		{
 			BIO_free(bio);
@@ -1531,14 +1531,14 @@ be_tls_get_certificate_hash(Port *port, size_t *len)
  *
  */
 static char *
-X509_NAME_to_cstring(X509_NAME *name)
+X509_NAME_to_cstring(const X509_NAME *name)
 {
 	BIO		   *membuf = BIO_new(BIO_s_mem());
 	int			i,
 				nid,
-				count = X509_NAME_entry_count(name);
-	X509_NAME_ENTRY *e;
-	ASN1_STRING *v;
+				count = X509_NAME_entry_count(unconstify(X509_NAME *, name));
+	const X509_NAME_ENTRY *e;
+	const ASN1_STRING *v;
 	const char *field_name;
 	size_t		size;
 	char		nullterm;
@@ -1554,13 +1554,13 @@ X509_NAME_to_cstring(X509_NAME *name)
 	(void) BIO_set_close(membuf, BIO_CLOSE);
 	for (i = 0; i < count; i++)
 	{
-		e = X509_NAME_get_entry(name, i);
-		nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
+		e = X509_NAME_get_entry(unconstify(X509_NAME *, name), i);
+		nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(unconstify(X509_NAME_ENTRY *, e)));
 		if (nid == NID_undef)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("could not get NID for ASN1_OBJECT object")));
-		v = X509_NAME_ENTRY_get_data(e);
+		v = X509_NAME_ENTRY_get_data(unconstify(X509_NAME_ENTRY *, e));
 		field_name = OBJ_nid2sn(nid);
 		if (field_name == NULL)
 			field_name = OBJ_nid2ln(nid);
@@ -1569,7 +1569,7 @@ X509_NAME_to_cstring(X509_NAME *name)
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
 		BIO_printf(membuf, "/%s=", field_name);
-		ASN1_STRING_print_ex(membuf, v,
+		ASN1_STRING_print_ex(membuf, unconstify(ASN1_STRING *, v),
 							 ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
 							  | ASN1_STRFLGS_UTF8_CONVERT));
 	}
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index ee518d5b875..e9a1e88b17f 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -68,7 +68,7 @@
 
 static int	verify_cb(int ok, X509_STORE_CTX *ctx);
 static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
-															  ASN1_STRING *name_entry,
+															  const ASN1_STRING *name_entry,
 															  char **store_name);
 static int	openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
 															ASN1_OCTET_STRING *addr_entry,
@@ -498,7 +498,8 @@ cert_cb(SSL *ssl, void *arg)
  * into a plain C string.
  */
 static int
-openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
+openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
+												  const ASN1_STRING *name_entry,
 												  char **store_name)
 {
 	int			len;
@@ -517,7 +518,7 @@ openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *nam
 #ifdef HAVE_ASN1_STRING_GET0_DATA
 	namedata = ASN1_STRING_get0_data(name_entry);
 #else
-	namedata = ASN1_STRING_data(name_entry);
+	namedata = ASN1_STRING_data(unconstify(ASN1_STRING *, name_entry));
 #endif
 	len = ASN1_STRING_length(name_entry);
 
@@ -689,14 +690,14 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	 */
 	if (check_cn)
 	{
-		X509_NAME  *subject_name;
+		const X509_NAME *subject_name;
 
 		subject_name = X509_get_subject_name(conn->peer);
 		if (subject_name != NULL)
 		{
 			int			cn_index;
 
-			cn_index = X509_NAME_get_index_by_NID(subject_name,
+			cn_index = X509_NAME_get_index_by_NID(unconstify(X509_NAME *, subject_name),
 												  NID_commonName, -1);
 			if (cn_index >= 0)
 			{
@@ -704,7 +705,7 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
-																	   X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject_name, cn_index)),
+																	   X509_NAME_ENTRY_get_data(X509_NAME_get_entry(unconstify(X509_NAME *, subject_name), cn_index)),
 																	   &common_name);
 
 				if (common_name)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 7f90c4efdd6..f6e68e7e2aa 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -777,7 +777,7 @@ $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt "
 	  . sslkey('client-revoked.key'),
 	"certificate authorization fails with revoked client cert",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|,
+	expected_stderr => qr!SSL error: (ssl[a-z0-9/]*|tls) alert certificate revoked!,
 	# temporarily(?) skip this check due to timing issue
 	#	log_like => [
 	#		qr{Client certificate verification failed at depth 0: certificate revoked},
@@ -882,7 +882,7 @@ $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt "
 	  . sslkey('client-revoked.key'),
 	"certificate authorization fails with revoked client cert with server-side CRL directory",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|,
+	expected_stderr => qr!SSL error: (ssl[a-z0-9/]*|tls) alert certificate revoked!,
 	# temporarily(?) skip this check due to timing issue
 	#	log_like => [
 	#		qr{Client certificate verification failed at depth 0: certificate revoked},
-- 
2.39.3 (Apple Git-146)



  [application/octet-stream] v2-master-0001-Support-OpenSSL-4.patch (8.9K, ../../[email protected]/5-v2-master-0001-Support-OpenSSL-4.patch)
  download | inline diff:
From 9f9d6e796a68c1fb1bdb59c6bbe5d08f50845e8b Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Thu, 16 Apr 2026 15:20:13 +0200
Subject: [PATCH v2-master] Support OpenSSL 4

OpenSSL 4.0.0 changed some parameters and returnvalues to const, so
we need to update our declarations and subsequently cast away const-
ness from a few callsites to make libpq build without warnings. This
is tested with OpenSSL 1.1.1 through 4.0.0 as well as with LibreSSL.

There is also an errormessage change in OpenSSL 4.0.0 which needs to
be covered by our testharness.

Author: Daniel Gustafsson <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
 contrib/sslinfo/sslinfo.c                | 20 ++++++++++----------
 src/backend/libpq/be-secure-openssl.c    | 14 +++++++-------
 src/interfaces/libpq/fe-secure-openssl.c |  9 +++++----
 src/test/ssl/t/001_ssltests.pl           |  6 +++---
 4 files changed, 25 insertions(+), 24 deletions(-)

diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 2b9eb90b093..c4ae847880d 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -24,8 +24,8 @@ PG_MODULE_MAGIC_EXT(
 					.version = PG_VERSION
 );
 
-static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum ASN1_STRING_to_text(ASN1_STRING *str);
+static Datum X509_NAME_field_to_text(const X509_NAME *name, text *fieldName);
+static Datum ASN1_STRING_to_text(const ASN1_STRING *str);
 
 /*
  * Function context for data persisting over repeated calls.
@@ -148,7 +148,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
  * function.
  */
 static Datum
-ASN1_STRING_to_text(ASN1_STRING *str)
+ASN1_STRING_to_text(const ASN1_STRING *str)
 {
 	BIO		   *membuf;
 	size_t		size;
@@ -194,12 +194,12 @@ ASN1_STRING_to_text(ASN1_STRING *str)
  * part of name
  */
 static Datum
-X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
+X509_NAME_field_to_text(const X509_NAME *name, text *fieldName)
 {
 	char	   *string_fieldname;
 	int			nid,
 				index;
-	ASN1_STRING *data;
+	const ASN1_STRING *data;
 
 	string_fieldname = text_to_cstring(fieldName);
 	nid = OBJ_txt2nid(string_fieldname);
@@ -209,7 +209,7 @@ X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
 				 errmsg("invalid X.509 field name: \"%s\"",
 						string_fieldname)));
 	pfree(string_fieldname);
-	index = X509_NAME_get_index_by_NID(name, nid, -1);
+	index = X509_NAME_get_index_by_NID(unconstify(X509_NAME *, name), nid, -1);
 	if (index < 0)
 		return (Datum) 0;
 	data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, index));
@@ -421,8 +421,8 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 		HeapTuple	tuple;
 		Datum		result;
 		BIO		   *membuf;
-		X509_EXTENSION *ext;
-		ASN1_OBJECT *obj;
+		const X509_EXTENSION *ext;
+		const ASN1_OBJECT *obj;
 		int			nid;
 		int			len;
 
@@ -435,7 +435,7 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 
 		/* Get the extension from the certificate */
 		ext = X509_get_ext(cert, call_cntr);
-		obj = X509_EXTENSION_get_object(ext);
+		obj = X509_EXTENSION_get_object(unconstify(X509_EXTENSION *, ext));
 
 		/* Get the extension name */
 		nid = OBJ_obj2nid(obj);
@@ -448,7 +448,7 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 		nulls[0] = false;
 
 		/* Get the extension value */
-		if (X509V3_EXT_print(membuf, ext, 0, 0) <= 0)
+		if (X509V3_EXT_print(membuf, unconstify(X509_EXTENSION *, ext), 0, 0) <= 0)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("could not print extension value in certificate at position %d",
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 877851a73cd..b786aff2c1b 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -106,7 +106,7 @@ static void host_context_cleanup_cb(void *arg);
 static int	sni_clienthello_cb(SSL *ssl, int *al, void *arg);
 #endif
 
-static char *X509_NAME_to_cstring(X509_NAME *name);
+static char *X509_NAME_to_cstring(const X509_NAME *name);
 
 static SSL_CTX *SSL_context = NULL;
 static MemoryContext SSL_hosts_memcxt = NULL;
@@ -1071,18 +1071,18 @@ aloop:
 	if (port->peer != NULL)
 	{
 		int			len;
-		X509_NAME  *x509name = X509_get_subject_name(port->peer);
+		const X509_NAME *x509name = X509_get_subject_name(port->peer);
 		char	   *peer_dn;
 		BIO		   *bio = NULL;
 		BUF_MEM    *bio_buf = NULL;
 
-		len = X509_NAME_get_text_by_NID(x509name, NID_commonName, NULL, 0);
+		len = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, NULL, 0);
 		if (len != -1)
 		{
 			char	   *peer_cn;
 
 			peer_cn = MemoryContextAlloc(TopMemoryContext, len + 1);
-			r = X509_NAME_get_text_by_NID(x509name, NID_commonName, peer_cn,
+			r = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, peer_cn,
 										  len + 1);
 			peer_cn[len] = '\0';
 			if (r != len)
@@ -2333,14 +2333,14 @@ be_tls_get_certificate_hash(Port *port, size_t *len)
  *
  */
 static char *
-X509_NAME_to_cstring(X509_NAME *name)
+X509_NAME_to_cstring(const X509_NAME *name)
 {
 	BIO		   *membuf = BIO_new(BIO_s_mem());
 	int			i,
 				nid,
 				count = X509_NAME_entry_count(name);
-	X509_NAME_ENTRY *e;
-	ASN1_STRING *v;
+	const X509_NAME_ENTRY *e;
+	const ASN1_STRING *v;
 	const char *field_name;
 	size_t		size;
 	char		nullterm;
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index fbd3c63fb5d..6b44eeb68eb 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -67,7 +67,7 @@
 
 static int	verify_cb(int ok, X509_STORE_CTX *ctx);
 static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
-															  ASN1_STRING *name_entry,
+															  const ASN1_STRING *name_entry,
 															  char **store_name);
 static int	openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
 															ASN1_OCTET_STRING *addr_entry,
@@ -467,7 +467,8 @@ cert_cb(SSL *ssl, void *arg)
  * into a plain C string.
  */
 static int
-openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
+openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
+												  const ASN1_STRING *name_entry,
 												  char **store_name)
 {
 	int			len;
@@ -650,14 +651,14 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	 */
 	if (check_cn)
 	{
-		X509_NAME  *subject_name;
+		const X509_NAME *subject_name;
 
 		subject_name = X509_get_subject_name(conn->peer);
 		if (subject_name != NULL)
 		{
 			int			cn_index;
 
-			cn_index = X509_NAME_get_index_by_NID(subject_name,
+			cn_index = X509_NAME_get_index_by_NID(unconstify(X509_NAME *, subject_name),
 												  NID_commonName, -1);
 			if (cn_index >= 0)
 			{
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 0af887caa63..01f3573e1fd 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -885,7 +885,7 @@ $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt "
 	  . sslkey('client-revoked.key'),
 	"certificate authorization fails with revoked client cert",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|,
+	expected_stderr => qr!SSL error: (ssl[a-z0-9/]*|tls) alert certificate revoked!,
 	log_like => [
 		qr{Client certificate verification failed at depth 0: certificate revoked},
 		qr{Failed certificate data \(unverified\): subject "/CN=ssltestuser", serial number \d+, issuer "/CN=Test CA for PostgreSQL SSL regression test client certs"},
@@ -987,7 +987,7 @@ $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt "
 	  . sslkey('client-revoked.key'),
 	"certificate authorization fails with revoked client cert with server-side CRL directory",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|,
+	expected_stderr => qr!SSL error: (ssl[a-z0-9/]*|tls) alert certificate revoked!,
 	log_like => [
 		qr{Client certificate verification failed at depth 0: certificate revoked},
 		qr{Failed certificate data \(unverified\): subject "/CN=ssltestuser", serial number \d+, issuer "/CN=Test CA for PostgreSQL SSL regression test client certs"},
@@ -998,7 +998,7 @@ $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked-utf8.crt "
 	  . sslkey('client-revoked-utf8.key'),
 	"certificate authorization fails with revoked UTF-8 client cert with server-side CRL directory",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|,
+	expected_stderr => qr!SSL error: (ssl[a-z0-9/]*|tls) alert certificate revoked!,
 	log_like => [
 		qr{Client certificate verification failed at depth 0: certificate revoked},
 		qr{Failed certificate data \(unverified\): subject "/CN=\\xce\\x9f\\xce\\xb4\\xcf\\x85\\xcf\\x83\\xcf\\x83\\xce\\xad\\xce\\xb1\\xcf\\x82", serial number \d+, issuer "/CN=Test CA for PostgreSQL SSL regression test client certs"},
-- 
2.39.3 (Apple Git-146)



  [application/octet-stream] v2-REL_17-0001-Support-OpenSSL-4.patch (10.9K, ../../[email protected]/6-v2-REL_17-0001-Support-OpenSSL-4.patch)
  download | inline diff:
From 1863b8f6fe399748ab72ec52238a026514d830bd Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Thu, 7 May 2026 10:46:18 +0200
Subject: [PATCH v2-REL_17] Support OpenSSL 4

OpenSSL 4.0.0 changed some parameters and returnvalues to const, so
we need to update our declarations and subsequently cast away const-
ness from a few callsites to make libpq build without warnings. This
is tested with OpenSSL 1.1.1 through 4.0.0 as well as with LibreSSL.

There is also an errormessage change in OpenSSL 4.0.0 which needs to
be covered by our testharness.

Author: Daniel Gustafsson <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
 contrib/sslinfo/sslinfo.c                | 24 +++++++++++-----------
 src/backend/libpq/be-secure-openssl.c    | 26 ++++++++++++------------
 src/interfaces/libpq/fe-secure-openssl.c | 13 ++++++------
 src/test/ssl/t/001_ssltests.pl           |  4 ++--
 4 files changed, 34 insertions(+), 33 deletions(-)

diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5fd46b98741..4251ccfd174 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -32,8 +32,8 @@
 
 PG_MODULE_MAGIC;
 
-static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum ASN1_STRING_to_text(ASN1_STRING *str);
+static Datum X509_NAME_field_to_text(const X509_NAME *name, text *fieldName);
+static Datum ASN1_STRING_to_text(const ASN1_STRING *str);
 
 /*
  * Function context for data persisting over repeated calls.
@@ -156,7 +156,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
  * function.
  */
 static Datum
-ASN1_STRING_to_text(ASN1_STRING *str)
+ASN1_STRING_to_text(const ASN1_STRING *str)
 {
 	BIO		   *membuf;
 	size_t		size;
@@ -171,7 +171,7 @@ ASN1_STRING_to_text(ASN1_STRING *str)
 				(errcode(ERRCODE_OUT_OF_MEMORY),
 				 errmsg("could not create OpenSSL BIO structure")));
 	(void) BIO_set_close(membuf, BIO_CLOSE);
-	ASN1_STRING_print_ex(membuf, str,
+	ASN1_STRING_print_ex(membuf, unconstify(ASN1_STRING *, str),
 						 ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
 						  | ASN1_STRFLGS_UTF8_CONVERT));
 	/* ensure null termination of the BIO's content */
@@ -202,12 +202,12 @@ ASN1_STRING_to_text(ASN1_STRING *str)
  * part of name
  */
 static Datum
-X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
+X509_NAME_field_to_text(const X509_NAME *name, text *fieldName)
 {
 	char	   *string_fieldname;
 	int			nid,
 				index;
-	ASN1_STRING *data;
+	const ASN1_STRING *data;
 
 	string_fieldname = text_to_cstring(fieldName);
 	nid = OBJ_txt2nid(string_fieldname);
@@ -217,10 +217,10 @@ X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
 				 errmsg("invalid X.509 field name: \"%s\"",
 						string_fieldname)));
 	pfree(string_fieldname);
-	index = X509_NAME_get_index_by_NID(name, nid, -1);
+	index = X509_NAME_get_index_by_NID(unconstify(X509_NAME *, name), nid, -1);
 	if (index < 0)
 		return (Datum) 0;
-	data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, index));
+	data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(unconstify(X509_NAME *, name), index));
 	return ASN1_STRING_to_text(data);
 }
 
@@ -429,8 +429,8 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 		HeapTuple	tuple;
 		Datum		result;
 		BIO		   *membuf;
-		X509_EXTENSION *ext;
-		ASN1_OBJECT *obj;
+		const X509_EXTENSION *ext;
+		const ASN1_OBJECT *obj;
 		int			nid;
 		int			len;
 
@@ -443,7 +443,7 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 
 		/* Get the extension from the certificate */
 		ext = X509_get_ext(cert, call_cntr);
-		obj = X509_EXTENSION_get_object(ext);
+		obj = X509_EXTENSION_get_object(unconstify(X509_EXTENSION *, ext));
 
 		/* Get the extension name */
 		nid = OBJ_obj2nid(obj);
@@ -456,7 +456,7 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 		nulls[0] = false;
 
 		/* Get the extension value */
-		if (X509V3_EXT_print(membuf, ext, 0, 0) <= 0)
+		if (X509V3_EXT_print(membuf, unconstify(X509_EXTENSION *, ext), 0, 0) <= 0)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("could not print extension value in certificate at position %d",
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 486a66b0bf1..8c101528618 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -77,7 +77,7 @@ static bool initialize_dh(SSL_CTX *context, bool isServerStart);
 static bool initialize_ecdh(SSL_CTX *context, bool isServerStart);
 static const char *SSLerrmessage(unsigned long ecode);
 
-static char *X509_NAME_to_cstring(X509_NAME *name);
+static char *X509_NAME_to_cstring(const X509_NAME *name);
 
 static SSL_CTX *SSL_context = NULL;
 static bool SSL_initialized = false;
@@ -634,18 +634,18 @@ aloop:
 	if (port->peer != NULL)
 	{
 		int			len;
-		X509_NAME  *x509name = X509_get_subject_name(port->peer);
+		const X509_NAME *x509name = X509_get_subject_name(port->peer);
 		char	   *peer_dn;
 		BIO		   *bio = NULL;
 		BUF_MEM    *bio_buf = NULL;
 
-		len = X509_NAME_get_text_by_NID(x509name, NID_commonName, NULL, 0);
+		len = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, NULL, 0);
 		if (len != -1)
 		{
 			char	   *peer_cn;
 
 			peer_cn = MemoryContextAlloc(TopMemoryContext, len + 1);
-			r = X509_NAME_get_text_by_NID(x509name, NID_commonName, peer_cn,
+			r = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, peer_cn,
 										  len + 1);
 			peer_cn[len] = '\0';
 			if (r != len)
@@ -689,7 +689,7 @@ aloop:
 		 * which make regular expression matching a bit easier. Also note that
 		 * it prints the Subject fields in reverse order.
 		 */
-		if (X509_NAME_print_ex(bio, x509name, 0, XN_FLAG_RFC2253) == -1 ||
+		if (X509_NAME_print_ex(bio, unconstify(X509_NAME *, x509name), 0, XN_FLAG_RFC2253) == -1 ||
 			BIO_get_mem_ptr(bio, &bio_buf) <= 0)
 		{
 			BIO_free(bio);
@@ -1615,14 +1615,14 @@ be_tls_get_certificate_hash(Port *port, size_t *len)
  *
  */
 static char *
-X509_NAME_to_cstring(X509_NAME *name)
+X509_NAME_to_cstring(const X509_NAME *name)
 {
 	BIO		   *membuf = BIO_new(BIO_s_mem());
 	int			i,
 				nid,
-				count = X509_NAME_entry_count(name);
-	X509_NAME_ENTRY *e;
-	ASN1_STRING *v;
+				count = X509_NAME_entry_count(unconstify(X509_NAME *, name));
+	const X509_NAME_ENTRY *e;
+	const ASN1_STRING *v;
 	const char *field_name;
 	size_t		size;
 	char		nullterm;
@@ -1638,13 +1638,13 @@ X509_NAME_to_cstring(X509_NAME *name)
 	(void) BIO_set_close(membuf, BIO_CLOSE);
 	for (i = 0; i < count; i++)
 	{
-		e = X509_NAME_get_entry(name, i);
-		nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
+		e = X509_NAME_get_entry(unconstify(X509_NAME *, name), i);
+		nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(unconstify(X509_NAME_ENTRY *, e)));
 		if (nid == NID_undef)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("could not get NID for ASN1_OBJECT object")));
-		v = X509_NAME_ENTRY_get_data(e);
+		v = X509_NAME_ENTRY_get_data(unconstify(X509_NAME_ENTRY *, e));
 		field_name = OBJ_nid2sn(nid);
 		if (field_name == NULL)
 			field_name = OBJ_nid2ln(nid);
@@ -1653,7 +1653,7 @@ X509_NAME_to_cstring(X509_NAME *name)
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
 		BIO_printf(membuf, "/%s=", field_name);
-		ASN1_STRING_print_ex(membuf, v,
+		ASN1_STRING_print_ex(membuf, unconstify(ASN1_STRING *, v),
 							 ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
 							  | ASN1_STRFLGS_UTF8_CONVERT));
 	}
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index b6fffd7b9b0..33ce6d5ffe3 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -66,7 +66,7 @@
 
 static int	verify_cb(int ok, X509_STORE_CTX *ctx);
 static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
-															  ASN1_STRING *name_entry,
+															  const ASN1_STRING *name_entry,
 															  char **store_name);
 static int	openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
 															ASN1_OCTET_STRING *addr_entry,
@@ -489,7 +489,8 @@ cert_cb(SSL *ssl, void *arg)
  * into a plain C string.
  */
 static int
-openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
+openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
+												  const ASN1_STRING *name_entry,
 												  char **store_name)
 {
 	int			len;
@@ -508,7 +509,7 @@ openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *nam
 #ifdef HAVE_ASN1_STRING_GET0_DATA
 	namedata = ASN1_STRING_get0_data(name_entry);
 #else
-	namedata = ASN1_STRING_data(name_entry);
+	namedata = ASN1_STRING_data(unconstify(ASN1_STRING *, name_entry));
 #endif
 	len = ASN1_STRING_length(name_entry);
 
@@ -680,14 +681,14 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	 */
 	if (check_cn)
 	{
-		X509_NAME  *subject_name;
+		const X509_NAME *subject_name;
 
 		subject_name = X509_get_subject_name(conn->peer);
 		if (subject_name != NULL)
 		{
 			int			cn_index;
 
-			cn_index = X509_NAME_get_index_by_NID(subject_name,
+			cn_index = X509_NAME_get_index_by_NID(unconstify(X509_NAME *, subject_name),
 												  NID_commonName, -1);
 			if (cn_index >= 0)
 			{
@@ -695,7 +696,7 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 
 				(*names_examined)++;
 				rc = openssl_verify_peer_name_matches_certificate_name(conn,
-																	   X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject_name, cn_index)),
+																	   X509_NAME_ENTRY_get_data(X509_NAME_get_entry(unconstify(X509_NAME *, subject_name), cn_index)),
 																	   &common_name);
 
 				if (common_name)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 17dd3964b76..4880d8a75b9 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -773,7 +773,7 @@ $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt "
 	  . sslkey('client-revoked.key'),
 	"certificate authorization fails with revoked client cert",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|,
+	expected_stderr => qr!SSL error: (ssl[a-z0-9/]*|tls) alert certificate revoked!,
 	# temporarily(?) skip this check due to timing issue
 	#	log_like => [
 	#		qr{Client certificate verification failed at depth 0: certificate revoked},
@@ -878,7 +878,7 @@ $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt "
 	  . sslkey('client-revoked.key'),
 	"certificate authorization fails with revoked client cert with server-side CRL directory",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|,
+	expected_stderr => qr!SSL error: (ssl[a-z0-9/]*|tls) alert certificate revoked!,
 	# temporarily(?) skip this check due to timing issue
 	#	log_like => [
 	#		qr{Client certificate verification failed at depth 0: certificate revoked},
-- 
2.39.3 (Apple Git-146)



  [application/octet-stream] v2-REL_18-0001-Support-OpenSSL-4.patch (8.9K, ../../[email protected]/7-v2-REL_18-0001-Support-OpenSSL-4.patch)
  download | inline diff:
From 608d5402867cc581ff935cf15d9c25f7eba63703 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Thu, 16 Apr 2026 15:20:13 +0200
Subject: [PATCH v2-REL_18] Support OpenSSL 4

OpenSSL 4.0.0 changed some parameters and returnvalues to const, so
we need to update our declarations and subsequently cast away const-
ness from a few callsites to make libpq build without warnings. This
is tested with OpenSSL 1.1.1 through 4.0.0 as well as with LibreSSL.

There is also an errormessage change in OpenSSL 4.0.0 which needs to
be covered by our testharness.

Author: Daniel Gustafsson <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
 contrib/sslinfo/sslinfo.c                | 20 ++++++++++----------
 src/backend/libpq/be-secure-openssl.c    | 14 +++++++-------
 src/interfaces/libpq/fe-secure-openssl.c |  9 +++++----
 src/test/ssl/t/001_ssltests.pl           |  6 +++---
 4 files changed, 25 insertions(+), 24 deletions(-)

diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index da702011193..9191bbce5dc 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -24,8 +24,8 @@ PG_MODULE_MAGIC_EXT(
 					.version = PG_VERSION
 );
 
-static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum ASN1_STRING_to_text(ASN1_STRING *str);
+static Datum X509_NAME_field_to_text(const X509_NAME *name, text *fieldName);
+static Datum ASN1_STRING_to_text(const ASN1_STRING *str);
 
 /*
  * Function context for data persisting over repeated calls.
@@ -148,7 +148,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
  * function.
  */
 static Datum
-ASN1_STRING_to_text(ASN1_STRING *str)
+ASN1_STRING_to_text(const ASN1_STRING *str)
 {
 	BIO		   *membuf;
 	size_t		size;
@@ -194,12 +194,12 @@ ASN1_STRING_to_text(ASN1_STRING *str)
  * part of name
  */
 static Datum
-X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
+X509_NAME_field_to_text(const X509_NAME *name, text *fieldName)
 {
 	char	   *string_fieldname;
 	int			nid,
 				index;
-	ASN1_STRING *data;
+	const ASN1_STRING *data;
 
 	string_fieldname = text_to_cstring(fieldName);
 	nid = OBJ_txt2nid(string_fieldname);
@@ -209,7 +209,7 @@ X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
 				 errmsg("invalid X.509 field name: \"%s\"",
 						string_fieldname)));
 	pfree(string_fieldname);
-	index = X509_NAME_get_index_by_NID(name, nid, -1);
+	index = X509_NAME_get_index_by_NID(unconstify(X509_NAME *, name), nid, -1);
 	if (index < 0)
 		return (Datum) 0;
 	data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, index));
@@ -421,8 +421,8 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 		HeapTuple	tuple;
 		Datum		result;
 		BIO		   *membuf;
-		X509_EXTENSION *ext;
-		ASN1_OBJECT *obj;
+		const X509_EXTENSION *ext;
+		const ASN1_OBJECT *obj;
 		int			nid;
 		int			len;
 
@@ -435,7 +435,7 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 
 		/* Get the extension from the certificate */
 		ext = X509_get_ext(cert, call_cntr);
-		obj = X509_EXTENSION_get_object(ext);
+		obj = X509_EXTENSION_get_object(unconstify(X509_EXTENSION *, ext));
 
 		/* Get the extension name */
 		nid = OBJ_obj2nid(obj);
@@ -448,7 +448,7 @@ ssl_extension_info(PG_FUNCTION_ARGS)
 		nulls[0] = false;
 
 		/* Get the extension value */
-		if (X509V3_EXT_print(membuf, ext, 0, 0) <= 0)
+		if (X509V3_EXT_print(membuf, unconstify(X509_EXTENSION *, ext), 0, 0) <= 0)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("could not print extension value in certificate at position %d",
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index c8b63ef8249..b67b91a54b2 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -78,7 +78,7 @@ static bool initialize_ecdh(SSL_CTX *context, bool isServerStart);
 static const char *SSLerrmessageExt(unsigned long ecode, const char *replacement);
 static const char *SSLerrmessage(unsigned long ecode);
 
-static char *X509_NAME_to_cstring(X509_NAME *name);
+static char *X509_NAME_to_cstring(const X509_NAME *name);
 
 static SSL_CTX *SSL_context = NULL;
 static bool dummy_ssl_passwd_cb_called = false;
@@ -638,18 +638,18 @@ aloop:
 	if (port->peer != NULL)
 	{
 		int			len;
-		X509_NAME  *x509name = X509_get_subject_name(port->peer);
+		const X509_NAME *x509name = X509_get_subject_name(port->peer);
 		char	   *peer_dn;
 		BIO		   *bio = NULL;
 		BUF_MEM    *bio_buf = NULL;
 
-		len = X509_NAME_get_text_by_NID(x509name, NID_commonName, NULL, 0);
+		len = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, NULL, 0);
 		if (len != -1)
 		{
 			char	   *peer_cn;
 
 			peer_cn = MemoryContextAlloc(TopMemoryContext, len + 1);
-			r = X509_NAME_get_text_by_NID(x509name, NID_commonName, peer_cn,
+			r = X509_NAME_get_text_by_NID(unconstify(X509_NAME *, x509name), NID_commonName, peer_cn,
 										  len + 1);
 			peer_cn[len] = '\0';
 			if (r != len)
@@ -1642,14 +1642,14 @@ be_tls_get_certificate_hash(Port *port, size_t *len)
  *
  */
 static char *
-X509_NAME_to_cstring(X509_NAME *name)
+X509_NAME_to_cstring(const X509_NAME *name)
 {
 	BIO		   *membuf = BIO_new(BIO_s_mem());
 	int			i,
 				nid,
 				count = X509_NAME_entry_count(name);
-	X509_NAME_ENTRY *e;
-	ASN1_STRING *v;
+	const X509_NAME_ENTRY *e;
+	const ASN1_STRING *v;
 	const char *field_name;
 	size_t		size;
 	char		nullterm;
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index d2045c73ae6..1dd9ba2f506 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -67,7 +67,7 @@
 
 static int	verify_cb(int ok, X509_STORE_CTX *ctx);
 static int	openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
-															  ASN1_STRING *name_entry,
+															  const ASN1_STRING *name_entry,
 															  char **store_name);
 static int	openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
 															ASN1_OCTET_STRING *addr_entry,
@@ -467,7 +467,8 @@ cert_cb(SSL *ssl, void *arg)
  * into a plain C string.
  */
 static int
-openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
+openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
+												  const ASN1_STRING *name_entry,
 												  char **store_name)
 {
 	int			len;
@@ -650,14 +651,14 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
 	 */
 	if (check_cn)
 	{
-		X509_NAME  *subject_name;
+		const X509_NAME *subject_name;
 
 		subject_name = X509_get_subject_name(conn->peer);
 		if (subject_name != NULL)
 		{
 			int			cn_index;
 
-			cn_index = X509_NAME_get_index_by_NID(subject_name,
+			cn_index = X509_NAME_get_index_by_NID(unconstify(X509_NAME *, subject_name),
 												  NID_commonName, -1);
 			if (cn_index >= 0)
 			{
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 310d70a4c08..973399b63d0 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -819,7 +819,7 @@ $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt "
 	  . sslkey('client-revoked.key'),
 	"certificate authorization fails with revoked client cert",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|,
+	expected_stderr => qr!SSL error: (ssl[a-z0-9/]*|tls) alert certificate revoked!,
 	log_like => [
 		qr{Client certificate verification failed at depth 0: certificate revoked},
 		qr{Failed certificate data \(unverified\): subject "/CN=ssltestuser", serial number \d+, issuer "/CN=Test CA for PostgreSQL SSL regression test client certs"},
@@ -921,7 +921,7 @@ $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt "
 	  . sslkey('client-revoked.key'),
 	"certificate authorization fails with revoked client cert with server-side CRL directory",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|,
+	expected_stderr => qr!SSL error: (ssl[a-z0-9/]*|tls) alert certificate revoked!,
 	log_like => [
 		qr{Client certificate verification failed at depth 0: certificate revoked},
 		qr{Failed certificate data \(unverified\): subject "/CN=ssltestuser", serial number \d+, issuer "/CN=Test CA for PostgreSQL SSL regression test client certs"},
@@ -932,7 +932,7 @@ $node->connect_fails(
 	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked-utf8.crt "
 	  . sslkey('client-revoked-utf8.key'),
 	"certificate authorization fails with revoked UTF-8 client cert with server-side CRL directory",
-	expected_stderr => qr|SSL error: ssl[a-z0-9/]* alert certificate revoked|,
+	expected_stderr => qr!SSL error: (ssl[a-z0-9/]*|tls) alert certificate revoked!,
 	log_like => [
 		qr{Client certificate verification failed at depth 0: certificate revoked},
 		qr{Failed certificate data \(unverified\): subject "/CN=\\xce\\x9f\\xce\\xb4\\xcf\\x85\\xcf\\x83\\xcf\\x83\\xce\\xad\\xce\\xb1\\xcf\\x82", serial number \d+, issuer "/CN=Test CA for PostgreSQL SSL regression test client certs"},
-- 
2.39.3 (Apple Git-146)



^ permalink  raw  reply  [nested|flat] 124+ messages in thread

* Re: PostgreSQL and OpenSSL 4.0.0
  2026-04-16 13:32 PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-04-17 21:50 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 13:44   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-07 22:13     ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 22:22       ` Re: PostgreSQL and OpenSSL 4.0.0 Tom Lane <[email protected]>
  2026-05-08 07:07         ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-08 07:17           ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-08 07:21             ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-19 21:18               ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
@ 2026-05-26 06:02                 ` Michael Paquier <[email protected]>
  2026-05-26 08:16                   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  0 siblings, 1 reply; 124+ messages in thread

From: Michael Paquier @ 2026-05-26 06:02 UTC (permalink / raw)
  To: Daniel Gustafsson <[email protected]>; +Cc: Tom Lane <[email protected]>; PostgreSQL-development <[email protected]>

On Tue, May 19, 2026 at 02:18:08PM -0700, Daniel Gustafsson wrote:
> > On 8 May 2026, at 00:21, Daniel Gustafsson <[email protected]> wrote:
> 
> > I think the changes are straightforward enough that we can go ahead with them.
> > I'll re-test and re-post a new patchset for all branches once the minors ship.
> 
> Attached are rebased versions of this patchset for v14-master.

I have a question here.  Most of the changes relate to the use of const
where the OpenSSL APIs require these to be so, but why is this a new
requirement for 4.0?  I can see that for most of the upstream
routines, the const changes are much older, like in 8cc86b81ac20 for
X509_NAME_get_text_by_NID() applying down to branch openssl-3.0.
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 124+ messages in thread

* Re: PostgreSQL and OpenSSL 4.0.0
  2026-04-16 13:32 PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-04-17 21:50 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 13:44   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-07 22:13     ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 22:22       ` Re: PostgreSQL and OpenSSL 4.0.0 Tom Lane <[email protected]>
  2026-05-08 07:07         ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-08 07:17           ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-08 07:21             ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-19 21:18               ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-26 06:02                 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
@ 2026-05-26 08:16                   ` Daniel Gustafsson <[email protected]>
  2026-05-27 00:06                     ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  0 siblings, 1 reply; 124+ messages in thread

From: Daniel Gustafsson @ 2026-05-26 08:16 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Tom Lane <[email protected]>; PostgreSQL-development <[email protected]>

> On 26 May 2026, at 08:02, Michael Paquier <[email protected]> wrote:
> 
> On Tue, May 19, 2026 at 02:18:08PM -0700, Daniel Gustafsson wrote:
>>> On 8 May 2026, at 00:21, Daniel Gustafsson <[email protected]> wrote:
>> 
>>> I think the changes are straightforward enough that we can go ahead with them.
>>> I'll re-test and re-post a new patchset for all branches once the minors ship.
>> 
>> Attached are rebased versions of this patchset for v14-master.
> 
> I have a question here.  Most of the changes relate to the use of const
> where the OpenSSL APIs require these to be so, but why is this a new
> requirement for 4.0?  I can see that for most of the upstream
> routines, the const changes are much older, like in 8cc86b81ac20 for
> X509_NAME_get_text_by_NID() applying down to branch openssl-3.0.

It is very true that OpenSSL has been constifying the API over time, the change
in 4.0 revolves around making more returnvalues const.  In the case at hand,
X509_get_subject_name() now returns a const X509_NAME pointer since commit
b0f2107b4404.  This wouldn't be a problem since X509_NAME_get_text_by_NID()
does as you say take a const parameter, but since we have shoehorned LibreSSL
support into the same file we are tied to the least common denominator and
LibreSSL is far behind OpenSSL on constifying.  So in this case, we need to
unconstify() to keep LibreSSL compiling.

I have plans for fixing this in v20 but for 14-19 there isn't much we can do
except unconstifying.

--
Daniel Gustafsson






^ permalink  raw  reply  [nested|flat] 124+ messages in thread

* Re: PostgreSQL and OpenSSL 4.0.0
  2026-04-16 13:32 PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-04-17 21:50 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 13:44   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-07 22:13     ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 22:22       ` Re: PostgreSQL and OpenSSL 4.0.0 Tom Lane <[email protected]>
  2026-05-08 07:07         ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-08 07:17           ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-08 07:21             ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-19 21:18               ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-26 06:02                 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-26 08:16                   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
@ 2026-05-27 00:06                     ` Michael Paquier <[email protected]>
  2026-05-27 10:47                       ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  0 siblings, 1 reply; 124+ messages in thread

From: Michael Paquier @ 2026-05-27 00:06 UTC (permalink / raw)
  To: Daniel Gustafsson <[email protected]>; +Cc: Tom Lane <[email protected]>; PostgreSQL-development <[email protected]>

On Tue, May 26, 2026 at 10:16:40AM +0200, Daniel Gustafsson wrote:
> I have plans for fixing this in v20 but for 14-19 there isn't much we can do
> except unconstifying.

Indeed, no objections regarding that for the stable branches.  For
v20, it sounds to me that cutting through the set of versions of
OpenSSL supported should make the situation much saner, even if the
range of changes seems to be limited due to the LibreSSL story.
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 124+ messages in thread

* Re: PostgreSQL and OpenSSL 4.0.0
  2026-04-16 13:32 PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-04-17 21:50 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 13:44   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-07 22:13     ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 22:22       ` Re: PostgreSQL and OpenSSL 4.0.0 Tom Lane <[email protected]>
  2026-05-08 07:07         ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-08 07:17           ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-08 07:21             ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-19 21:18               ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-26 06:02                 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-26 08:16                   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-27 00:06                     ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
@ 2026-05-27 10:47                       ` Daniel Gustafsson <[email protected]>
  2026-05-29 20:05                         ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  0 siblings, 1 reply; 124+ messages in thread

From: Daniel Gustafsson @ 2026-05-27 10:47 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Tom Lane <[email protected]>; PostgreSQL-development <[email protected]>

> On 27 May 2026, at 02:06, Michael Paquier <[email protected]> wrote:
> 
> On Tue, May 26, 2026 at 10:16:40AM +0200, Daniel Gustafsson wrote:
>> I have plans for fixing this in v20 but for 14-19 there isn't much we can do
>> except unconstifying.
> 
> Indeed, no objections regarding that for the stable branches.

Thanks for confirming.  I am currently re-testing all the combinations of stable
postgres branches and supported OpenSSL and LibreSSL versions.

> For v20, it sounds to me that cutting through the set of versions of
> OpenSSL supported should make the situation much saner, even if the
> range of changes seems to be limited due to the LibreSSL story.

Not to thread-jack myself, but.  I have a WIP patch for v20 which separates the
code into {fe|be}-secure-openssl.c and {fe[be}-secure-libressl such that we can
start modernizing our OpenSSL code without breaking LibreSSL or risk ending up
with an impenetrable ifdef soup.  Will share shortly to get a discussion going
for how we want to deal with TLS support in 20 and onwards.

--
Daniel Gustafsson







^ permalink  raw  reply  [nested|flat] 124+ messages in thread

* Re: PostgreSQL and OpenSSL 4.0.0
  2026-04-16 13:32 PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-04-17 21:50 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 13:44   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-07 22:13     ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 22:22       ` Re: PostgreSQL and OpenSSL 4.0.0 Tom Lane <[email protected]>
  2026-05-08 07:07         ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-08 07:17           ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-08 07:21             ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-19 21:18               ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-26 06:02                 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-26 08:16                   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-27 00:06                     ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-27 10:47                       ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
@ 2026-05-29 20:05                         ` Daniel Gustafsson <[email protected]>
  2026-06-12 13:15                           ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  0 siblings, 1 reply; 124+ messages in thread

From: Daniel Gustafsson @ 2026-05-29 20:05 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Tom Lane <[email protected]>; PostgreSQL-development <[email protected]>

> On 27 May 2026, at 12:47, Daniel Gustafsson <[email protected]> wrote:
> 
>> On 27 May 2026, at 02:06, Michael Paquier <[email protected]> wrote:
>> 
>> On Tue, May 26, 2026 at 10:16:40AM +0200, Daniel Gustafsson wrote:
>>> I have plans for fixing this in v20 but for 14-19 there isn't much we can do
>>> except unconstifying.
>> 
>> Indeed, no objections regarding that for the stable branches.
> 
> Thanks for confirming.  I am currently re-testing all the combinations of stable
> postgres branches and supported OpenSSL and LibreSSL versions.

While I had hoped to get it done sooner (sickness altered my plans), I just
pushed this to master ahead of the beta1 deadline, and once it's been through
some rounds of buildfarm compiles I'll backpatch to the stable branches.

--
Daniel Gustafsson







^ permalink  raw  reply  [nested|flat] 124+ messages in thread

* Re: PostgreSQL and OpenSSL 4.0.0
  2026-04-16 13:32 PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-04-17 21:50 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 13:44   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-07 22:13     ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-07 22:22       ` Re: PostgreSQL and OpenSSL 4.0.0 Tom Lane <[email protected]>
  2026-05-08 07:07         ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-08 07:17           ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-08 07:21             ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-19 21:18               ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-26 06:02                 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-26 08:16                   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-27 00:06                     ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
  2026-05-27 10:47                       ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
  2026-05-29 20:05                         ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
@ 2026-06-12 13:15                           ` Daniel Gustafsson <[email protected]>
  0 siblings, 0 replies; 124+ messages in thread

From: Daniel Gustafsson @ 2026-06-12 13:15 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Tom Lane <[email protected]>; PostgreSQL-development <[email protected]>

> On 29 May 2026, at 22:05, Daniel Gustafsson <[email protected]> wrote:
> 
>> On 27 May 2026, at 12:47, Daniel Gustafsson <[email protected]> wrote:
>> 
>>> On 27 May 2026, at 02:06, Michael Paquier <[email protected]> wrote:
>>> 
>>> On Tue, May 26, 2026 at 10:16:40AM +0200, Daniel Gustafsson wrote:
>>>> I have plans for fixing this in v20 but for 14-19 there isn't much we can do
>>>> except unconstifying.
>>> 
>>> Indeed, no objections regarding that for the stable branches.
>> 
>> Thanks for confirming.  I am currently re-testing all the combinations of stable
>> postgres branches and supported OpenSSL and LibreSSL versions.
> 
> While I had hoped to get it done sooner (sickness altered my plans), I just
> pushed this to master ahead of the beta1 deadline, and once it's been through
> some rounds of buildfarm compiles I'll backpatch to the stable branches.

I've now pushed this to all supported branches after recompiling and retesting
all the backbranches against all the versions of OpenSSL and LibreSSL we
support.

--
Daniel Gustafsson







^ permalink  raw  reply  [nested|flat] 124+ messages in thread


end of thread, other threads:[~2026-06-12 13:15 UTC | newest]

Thread overview: 124+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
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 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 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 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 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 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 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 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 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-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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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]>
2026-04-16 13:32 PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
2026-04-17 21:50 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
2026-05-07 13:44   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
2026-05-07 19:32     ` Re: PostgreSQL and OpenSSL 4.0.0 Cary Huang <[email protected]>
2026-05-07 19:39       ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
2026-05-07 19:51     ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
2026-05-07 22:13     ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
2026-05-07 22:22       ` Re: PostgreSQL and OpenSSL 4.0.0 Tom Lane <[email protected]>
2026-05-07 22:54         ` Re: PostgreSQL and OpenSSL 4.0.0 Cary Huang <[email protected]>
2026-05-08 07:07         ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
2026-05-08 07:17           ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
2026-05-08 07:21             ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
2026-05-19 21:18               ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
2026-05-26 06:02                 ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
2026-05-26 08:16                   ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
2026-05-27 00:06                     ` Re: PostgreSQL and OpenSSL 4.0.0 Michael Paquier <[email protected]>
2026-05-27 10:47                       ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
2026-05-29 20:05                         ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[email protected]>
2026-06-12 13:15                           ` Re: PostgreSQL and OpenSSL 4.0.0 Daniel Gustafsson <[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