Hi. Attached patch enables throwing of errors in jsonb_path_match() in its non-silent mode when the jsonpath expression failed to return a singleton boolean. Previously, NULL was always returned, and it seemed to be inconsistent with the behavior of other functions, in which structural and other errors were not suppressed in non-silent mode.
We also think that jsonb_path_match() needs to be renamed, because its current name is confusing to many users. "Match" name is more suitable for jsonpath-based pattern matching like that (maybe we'll implement it later): jsonb_path_match( '{ "a": 1, "b": 2, "c": "str" }', '{ "a": 1, "b": @ > 1, * : @.type == "string" }' ) Below are some possible name variants: jsonb_path_predicate() (original name) jsonb_path_pred() jsonb_path_test() jsonb_path_check() jsonb_path_bool() -- Nikita Glukhov Postgres Professional: http://www.postgrespro.com The Russian Postgres Company
>From f298aaa1258c6a4b4a487fb44980654b3a8e2e36 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov <n.glu...@postgrespro.ru> Date: Fri, 22 Mar 2019 02:34:24 +0300 Subject: [PATCH] Throw error in jsonb_path_match() when silent is false --- src/backend/utils/adt/jsonpath_exec.c | 26 +++++++++----- src/test/regress/expected/jsonb_jsonpath.out | 51 ++++++++++++++++++++++++++++ src/test/regress/sql/jsonb_jsonpath.sql | 12 +++++++ 3 files changed, 80 insertions(+), 9 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index c072257..074cea2 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -320,7 +320,6 @@ jsonb_path_match(PG_FUNCTION_ARGS) { Jsonb *jb = PG_GETARG_JSONB_P(0); JsonPath *jp = PG_GETARG_JSONPATH_P(1); - JsonbValue *jbv; JsonValueList found = {0}; Jsonb *vars = NULL; bool silent = true; @@ -333,18 +332,27 @@ jsonb_path_match(PG_FUNCTION_ARGS) (void) executeJsonPath(jp, vars, jb, !silent, &found); - if (JsonValueListLength(&found) < 1) - PG_RETURN_NULL(); - - jbv = JsonValueListHead(&found); - PG_FREE_IF_COPY(jb, 0); PG_FREE_IF_COPY(jp, 1); - if (jbv->type != jbvBool) - PG_RETURN_NULL(); + if (JsonValueListLength(&found) == 1) + { + JsonbValue *jbv = JsonValueListHead(&found); + + if (jbv->type == jbvBool) + PG_RETURN_BOOL(jbv->val.boolean); + + if (jbv->type == jbvNull) + PG_RETURN_NULL(); + } + + if (!silent) + ereport(ERROR, + (errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED), + errmsg(ERRMSG_SINGLETON_JSON_ITEM_REQUIRED), + errdetail("expression should return a singleton boolean"))); - PG_RETURN_BOOL(jbv->val.boolean); + PG_RETURN_NULL(); } /* diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index e604bae..66f0ffd 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -1769,6 +1769,57 @@ SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@. f (1 row) +SELECT jsonb_path_match('true', '$', silent => false); + jsonb_path_match +------------------ + t +(1 row) + +SELECT jsonb_path_match('false', '$', silent => false); + jsonb_path_match +------------------ + f +(1 row) + +SELECT jsonb_path_match('null', '$', silent => false); + jsonb_path_match +------------------ + +(1 row) + +SELECT jsonb_path_match('1', '$', silent => true); + jsonb_path_match +------------------ + +(1 row) + +SELECT jsonb_path_match('1', '$', silent => false); +ERROR: singleton SQL/JSON item required +DETAIL: expression should return a singleton boolean +SELECT jsonb_path_match('"a"', '$', silent => false); +ERROR: singleton SQL/JSON item required +DETAIL: expression should return a singleton boolean +SELECT jsonb_path_match('{}', '$', silent => false); +ERROR: singleton SQL/JSON item required +DETAIL: expression should return a singleton boolean +SELECT jsonb_path_match('[true]', '$', silent => false); +ERROR: singleton SQL/JSON item required +DETAIL: expression should return a singleton boolean +SELECT jsonb_path_match('{}', 'lax $.a', silent => false); +ERROR: singleton SQL/JSON item required +DETAIL: expression should return a singleton boolean +SELECT jsonb_path_match('{}', 'strict $.a', silent => false); +ERROR: SQL/JSON member not found +DETAIL: JSON object does not contain key "a" +SELECT jsonb_path_match('{}', 'strict $.a', silent => true); + jsonb_path_match +------------------ + +(1 row) + +SELECT jsonb_path_match('[true, true]', '$[*]', silent => false); +ERROR: singleton SQL/JSON item required +DETAIL: expression should return a singleton boolean SELECT jsonb '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 1'; ?column? ---------- diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 41b346b..f8ef39c 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -366,6 +366,18 @@ SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 1)'); SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', vars => '{"min": 1, "max": 4}'); SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', vars => '{"min": 3, "max": 4}'); +SELECT jsonb_path_match('true', '$', silent => false); +SELECT jsonb_path_match('false', '$', silent => false); +SELECT jsonb_path_match('null', '$', silent => false); +SELECT jsonb_path_match('1', '$', silent => true); +SELECT jsonb_path_match('1', '$', silent => false); +SELECT jsonb_path_match('"a"', '$', silent => false); +SELECT jsonb_path_match('{}', '$', silent => false); +SELECT jsonb_path_match('[true]', '$', silent => false); +SELECT jsonb_path_match('{}', 'lax $.a', silent => false); +SELECT jsonb_path_match('{}', 'strict $.a', silent => false); +SELECT jsonb_path_match('{}', 'strict $.a', silent => true); +SELECT jsonb_path_match('[true, true]', '$[*]', silent => false); SELECT jsonb '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 1'; SELECT jsonb '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 2'; SELECT jsonb_path_match('[{"a": 1}, {"a": 2}]', '$[*].a > 1'); -- 2.7.4