From 12df9d09035a29239fd93ca600f25a902a19cd06 Mon Sep 17 00:00:00 2001
From: "David E. Wheeler" <david@justatheory.com>
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

