From 12df9d09035a29239fd93ca600f25a902a19cd06 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 4 Jun 2024 12:00:25 -0400 Subject: [PATCH] Fix behavior of jsonpath `.*` on arrays The behavior of the `.*` jpiAnyKey jsonpath selector seems incorrect: ``` select jsonb_path_query('[1,2,3]', '$.*'); jsonb_path_query ------------------ (0 rows) select jsonb_path_query('[1,2,3,{"b": [3,4,5]}]', '$.*'); jsonb_path_query ------------------ [3, 4, 5] ``` The first example might be expected, since `.*` is intended for object keys, but the handing of `jpiAnyKey` has a branch for unwrapping arrays. The second example, however, just seems weird: this is `.*`, not `.**`. Fix it by passing the next node to `executeAnyItem()` (via `executeItemUnwrapTargetArray()`) and then properly set `jperOk` when `executeAnyItem()` finds values when there is no current (next) node. While at it, document a couple functions. --- src/backend/utils/adt/jsonpath_exec.c | 17 ++++++--- src/test/regress/expected/jsonb_jsonpath.out | 36 ++++++++++++++++++++ src/test/regress/sql/jsonb_jsonpath.sql | 6 ++++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 8a0a2dbc85..f09cd39342 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -864,8 +864,10 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, jb->val.binary.data, found, 1, 1, 1, false, jspAutoUnwrap(cxt)); } - else if (unwrap && JsonbType(jb) == jbvArray) - return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false); + else if (unwrap && JsonbType(jb) == jbvArray) { + bool hasNext = jspGetNext(jsp, &elem); + return executeItemUnwrapTargetArray(cxt, hasNext ? &elem : NULL, jb, found, false); + } else if (!jspIgnoreStructuralErrors(cxt)) { Assert(found); @@ -2002,8 +2004,10 @@ executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc, if (res == jperOk && !found) break; } - else if (found) + else if (found) { JsonValueListAppend(found, copyJsonbValue(&v)); + res = jperOk; + } else return jperOk; } @@ -2976,7 +2980,8 @@ getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, } /* - * Returns the computed value of a JSON path variable with given name. + * Definition of JsonPathGetVarCallback for when JsonPathExecContext.vars + * is specified as a List value. */ static JsonbValue * GetJsonPathVar(void *cxt, char *varName, int varNameLen, @@ -3022,6 +3027,10 @@ GetJsonPathVar(void *cxt, char *varName, int varNameLen, return result; } +/* + * Definition of JsonPathCountVarsCallback for when JsonPathExecContext.vars + * is specified as a List value. + */ static int CountJsonPathVars(void *cxt) { diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index c3f8e8249d..539884ead4 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -245,6 +245,42 @@ select jsonb_path_exists('[{"a": 1}, {"a": 2}, 3]', 'strict $[*].a', silent => t (1 row) +select jsonb_path_query('{"a": [1,2,3], "b": [3,4,5]}', '$.*'); + jsonb_path_query +------------------ + [1, 2, 3] + [3, 4, 5] +(2 rows) + +select jsonb_path_query('[1,2,3]', '$.*'); + jsonb_path_query +------------------ + 1 + 2 + 3 +(3 rows) + +select jsonb_path_query('[1,2,3,{"b": [3,4,5]}]', '$.*'); + jsonb_path_query +------------------ + 1 + 2 + 3 + {"b": [3, 4, 5]} +(4 rows) + +select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$.*'; + ?column? +---------- + t +(1 row) + +select jsonb '[1,2,3,{"b": [3,4,5]}]' @? '$.*'; + ?column? +---------- + t +(1 row) + select jsonb_path_query('1', 'lax $.a'); jsonb_path_query ------------------ diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index cbd2db533d..e650da10b6 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -44,6 +44,12 @@ select jsonb_path_exists('[{"a": 1}, {"a": 2}, 3]', 'lax $[*].a', silent => true select jsonb_path_exists('[{"a": 1}, {"a": 2}, 3]', 'strict $[*].a', silent => false); select jsonb_path_exists('[{"a": 1}, {"a": 2}, 3]', 'strict $[*].a', silent => true); +select jsonb_path_query('{"a": [1,2,3], "b": [3,4,5]}', '$.*'); +select jsonb_path_query('[1,2,3]', '$.*'); +select jsonb_path_query('[1,2,3,{"b": [3,4,5]}]', '$.*'); +select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$.*'; +select jsonb '[1,2,3,{"b": [3,4,5]}]' @? '$.*'; + select jsonb_path_query('1', 'lax $.a'); select jsonb_path_query('1', 'strict $.a'); select jsonb_path_query('1', 'strict $.*'); -- 2.45.2