From 1edb3b491bee373bdfcbfc1c2707b5428c2023b9 Mon Sep 17 00:00:00 2001 From: Amit Langote Date: Wed, 24 Jan 2024 13:35:28 +0900 Subject: Adjust populate_record_field() to handle errors softly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a Node *escontext parameter to it and a bunch of functions downstream to it, replacing any ereport()s in that path by either errsave() or ereturn() as appropriate. This also adds code to those functions where necessary to return early upon encountering a soft error. The changes here are mainly intended to suppress errors in the functions of jsonfuncs.c. Functions in any external modules, such as arrayfuncs.c, that those functions may in turn call are not changed here based on the assumption that the various checks in jsonfuncs.c functions should ensure that only values that are structurally valid get passed to the functions in those external modules. An exception is made for domain_check() to allow handling domain constraint violation errors softly. For testing, this adds a function jsonb_populate_record_valid(), which returns true if jsonb_populate_record() would finish without causing an error for the provided JSON object, false otherwise. Note that jsonb_populate_record() internally calls populate_record(), which in turn uses populate_record_field(). Extracted from a much larger patch to add SQL/JSON query functions. Author: Nikita Glukhov Author: Teodor Sigaev Author: Oleg Bartunov Author: Alexander Korotkov Author: Andrew Dunstan Author: Amit Langote Reviewers have included (in no particular order) Andres Freund, Alexander Korotkov, Pavel Stehule, Andrew Alsup, Erik Rijkers, Zihong Yu, Himanshu Upadhyaya, Daniel Gustafsson, Justin Pryzby, Álvaro Herrera, Jian He, Peter Eisentraut Discussion: https://postgr.es/m/cd0bb935-0158-78a7-08b5-904886deac4b@postgrespro.ru Discussion: https://postgr.es/m/20220616233130.rparivafipt6doj3@alap3.anarazel.de Discussion: https://postgr.es/m/abd9b83b-aa66-f230-3d6d-734817f0995d%40postgresql.org Discussion: https://postgr.es/m/CA+HiwqHROpf9e644D8BRqYvaAPmgBZVup-xKMDPk-nd4EpgzHw@mail.gmail.com Discussion: https://postgr.es/m/CA+HiwqE4XTdfb1nW=Ojoy_tQSRhYt-q_kb6i5d4xcKyrLC1Nbg@mail.gmail.com --- src/test/regress/expected/jsonb.out | 115 ++++++++++++++++++++++++++++++++++++ src/test/regress/sql/jsonb.sql | 37 ++++++++++++ 2 files changed, 152 insertions(+) (limited to 'src/test') diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out index b597d01a55e..66bee5162b4 100644 --- a/src/test/regress/expected/jsonb.out +++ b/src/test/regress/expected/jsonb.out @@ -2505,6 +2505,121 @@ SELECT rec FROM jsonb_populate_record( (abc,3,"Thu Jan 02 00:00:00 2003") (1 row) +-- Tests to check soft-error support for populate_record_field() +-- populate_scalar() +create type jsb_char2 as (a char(2)); +select jsonb_populate_record_valid(NULL::jsb_char2, '{"a": "aaa"}'); + jsonb_populate_record_valid +----------------------------- + f +(1 row) + +select * from jsonb_populate_record(NULL::jsb_char2, '{"a": "aaa"}') q; +ERROR: value too long for type character(2) +select jsonb_populate_record_valid(NULL::jsb_char2, '{"a": "aa"}'); + jsonb_populate_record_valid +----------------------------- + t +(1 row) + +select * from jsonb_populate_record(NULL::jsb_char2, '{"a": "aa"}') q; + a +---- + aa +(1 row) + +-- populate_array() +create type jsb_ia as (a int[]); +create type jsb_ia2 as (a int[][]); +select jsonb_populate_record_valid(NULL::jsb_ia, '{"a": 43.2}'); + jsonb_populate_record_valid +----------------------------- + f +(1 row) + +select * from jsonb_populate_record(NULL::jsb_ia, '{"a": 43.2}') q; +ERROR: expected JSON array +HINT: See the value of key "a". +select jsonb_populate_record_valid(NULL::jsb_ia, '{"a": [1, 2]}'); + jsonb_populate_record_valid +----------------------------- + t +(1 row) + +select * from jsonb_populate_record(NULL::jsb_ia, '{"a": [1, 2]}') q; + a +------- + {1,2} +(1 row) + +select jsonb_populate_record_valid(NULL::jsb_ia2, '{"a": [[1], [2, 3]]}'); + jsonb_populate_record_valid +----------------------------- + f +(1 row) + +select * from jsonb_populate_record(NULL::jsb_ia2, '{"a": [[1], [2, 3]]}') q; +ERROR: malformed JSON array +DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions. +select jsonb_populate_record_valid(NULL::jsb_ia2, '{"a": [[1, 0], [2, 3]]}'); + jsonb_populate_record_valid +----------------------------- + t +(1 row) + +select * from jsonb_populate_record(NULL::jsb_ia2, '{"a": [[1, 0], [2, 3]]}') q; + a +--------------- + {{1,0},{2,3}} +(1 row) + +-- populate_domain() +create domain jsb_i_not_null as int not null; +create domain jsb_i_gt_1 as int check (value > 1); +create type jsb_i_not_null_rec as (a jsb_i_not_null); +create type jsb_i_gt_1_rec as (a jsb_i_gt_1); +select jsonb_populate_record_valid(NULL::jsb_i_not_null_rec, '{"a": null}'); + jsonb_populate_record_valid +----------------------------- + f +(1 row) + +select * from jsonb_populate_record(NULL::jsb_i_not_null_rec, '{"a": null}') q; +ERROR: domain jsb_i_not_null does not allow null values +select jsonb_populate_record_valid(NULL::jsb_i_not_null_rec, '{"a": 1}'); + jsonb_populate_record_valid +----------------------------- + t +(1 row) + +select * from jsonb_populate_record(NULL::jsb_i_not_null_rec, '{"a": 1}') q; + a +--- + 1 +(1 row) + +select jsonb_populate_record_valid(NULL::jsb_i_gt_1_rec, '{"a": 1}'); + jsonb_populate_record_valid +----------------------------- + f +(1 row) + +select * from jsonb_populate_record(NULL::jsb_i_gt_1_rec, '{"a": 1}') q; +ERROR: value for domain jsb_i_gt_1 violates check constraint "jsb_i_gt_1_check" +select jsonb_populate_record_valid(NULL::jsb_i_gt_1_rec, '{"a": 2}'); + jsonb_populate_record_valid +----------------------------- + t +(1 row) + +select * from jsonb_populate_record(NULL::jsb_i_gt_1_rec, '{"a": 2}') q; + a +--- + 2 +(1 row) + +drop type jsb_ia, jsb_ia2, jsb_char2, jsb_i_not_null_rec, jsb_i_gt_1_rec; +drop domain jsb_i_not_null, jsb_i_gt_1; -- anonymous record type SELECT jsonb_populate_record(null::record, '{"x": 0, "y": 1}'); ERROR: could not determine row type for result of jsonb_populate_record diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql index 6dae715afd8..97bc2242a13 100644 --- a/src/test/regress/sql/jsonb.sql +++ b/src/test/regress/sql/jsonb.sql @@ -679,6 +679,43 @@ SELECT rec FROM jsonb_populate_record( '{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}' ) q; +-- Tests to check soft-error support for populate_record_field() + +-- populate_scalar() +create type jsb_char2 as (a char(2)); +select jsonb_populate_record_valid(NULL::jsb_char2, '{"a": "aaa"}'); +select * from jsonb_populate_record(NULL::jsb_char2, '{"a": "aaa"}') q; +select jsonb_populate_record_valid(NULL::jsb_char2, '{"a": "aa"}'); +select * from jsonb_populate_record(NULL::jsb_char2, '{"a": "aa"}') q; + +-- populate_array() +create type jsb_ia as (a int[]); +create type jsb_ia2 as (a int[][]); +select jsonb_populate_record_valid(NULL::jsb_ia, '{"a": 43.2}'); +select * from jsonb_populate_record(NULL::jsb_ia, '{"a": 43.2}') q; +select jsonb_populate_record_valid(NULL::jsb_ia, '{"a": [1, 2]}'); +select * from jsonb_populate_record(NULL::jsb_ia, '{"a": [1, 2]}') q; +select jsonb_populate_record_valid(NULL::jsb_ia2, '{"a": [[1], [2, 3]]}'); +select * from jsonb_populate_record(NULL::jsb_ia2, '{"a": [[1], [2, 3]]}') q; +select jsonb_populate_record_valid(NULL::jsb_ia2, '{"a": [[1, 0], [2, 3]]}'); +select * from jsonb_populate_record(NULL::jsb_ia2, '{"a": [[1, 0], [2, 3]]}') q; + +-- populate_domain() +create domain jsb_i_not_null as int not null; +create domain jsb_i_gt_1 as int check (value > 1); +create type jsb_i_not_null_rec as (a jsb_i_not_null); +create type jsb_i_gt_1_rec as (a jsb_i_gt_1); +select jsonb_populate_record_valid(NULL::jsb_i_not_null_rec, '{"a": null}'); +select * from jsonb_populate_record(NULL::jsb_i_not_null_rec, '{"a": null}') q; +select jsonb_populate_record_valid(NULL::jsb_i_not_null_rec, '{"a": 1}'); +select * from jsonb_populate_record(NULL::jsb_i_not_null_rec, '{"a": 1}') q; +select jsonb_populate_record_valid(NULL::jsb_i_gt_1_rec, '{"a": 1}'); +select * from jsonb_populate_record(NULL::jsb_i_gt_1_rec, '{"a": 1}') q; +select jsonb_populate_record_valid(NULL::jsb_i_gt_1_rec, '{"a": 2}'); +select * from jsonb_populate_record(NULL::jsb_i_gt_1_rec, '{"a": 2}') q; +drop type jsb_ia, jsb_ia2, jsb_char2, jsb_i_not_null_rec, jsb_i_gt_1_rec; +drop domain jsb_i_not_null, jsb_i_gt_1; + -- anonymous record type SELECT jsonb_populate_record(null::record, '{"x": 0, "y": 1}'); SELECT jsonb_populate_record(row(1,2), '{"f1": 0, "f2": 1}'); -- cgit v1.2.3