From 62fd2860abdaaad97741670e80dcd84cc7222925 Mon Sep 17 00:00:00 2001
From: "David E. Wheeler" <david@justatheory.com>
Date: Fri, 14 Jun 2024 12:02:05 -0400
Subject: [PATCH v2] Teach jsonpath string() to unwrap in lax mode

All other jsonpath methods, aside from type() and .size(), will unwrap
an array in lax mode, but string(), added in 66ea94e, overlooked this
behavior. A discussion on pgsql-hackers[1] cites the SQL standard:

> General Rule 11 g ii 6) A) says just "if MODE is lax and <JSON method>
> is not type or size, then let BASE be Unwrap(BASE)." No special
> exemption there for string(), nor further below at C) XV) for the
> operation of string().

So teach string() to also unwrap in lax mode, update the test for this
behavior, and add a line to the docs about the behavior of methods in
lax mode.

  [1]: https://www.postgresql.org/message-id/flat/A64AE04F-4410-42B7-A141-7A7349260F4D%40justatheory.com
---
 doc/src/sgml/func.sgml                       | 5 ++++-
 src/backend/utils/adt/jsonpath_exec.c        | 3 +++
 src/test/regress/expected/jsonb_jsonpath.out | 9 +++++++--
 src/test/regress/sql/jsonb_jsonpath.sql      | 2 +-
 4 files changed, 15 insertions(+), 4 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 17c44bc338..ededec932b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17792,7 +17792,10 @@ ERROR:  jsonpath member accessor can only be applied to an object
     methods available in <type>jsonpath</type>.  Note that while the unary
     operators and methods can be applied to multiple values resulting from a
     preceding path step, the binary operators (addition etc.) can only be
-    applied to single values.
+    applied to single values. In lax mode, methods applied to an array will be
+    executed for each value in the array. The exceptions are
+    <literal>.type()</literal> and <literal>.size()</literal>, which apply to
+    the array itself.
    </para>
 
    <table id="functions-sqljson-op-table">
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index ceb30033e1..c30d059a76 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -1606,6 +1606,9 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
 				JsonbValue	jbv;
 				char	   *tmp = NULL;
 
+				if (unwrap && JsonbType(jb) == jbvArray)
+					return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
+
 				switch (JsonbType(jb))
 				{
 					case jbvString:
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index c3f8e8249d..a13eee24fb 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -2524,8 +2524,13 @@ select jsonb_path_query('null', '$.string()', silent => true);
 ------------------
 (0 rows)
 
-select jsonb_path_query('[]', '$.string()');
-ERROR:  jsonpath item method .string() can only be applied to a bool, string, numeric, or datetime value
+select jsonb_path_query('[2, true]', '$.string()');
+ jsonb_path_query 
+------------------
+ "2"
+ "true"
+(2 rows)
+
 select jsonb_path_query('[]', 'strict $.string()');
 ERROR:  jsonpath item method .string() can only be applied to a bool, string, numeric, or datetime value
 select jsonb_path_query('{}', '$.string()');
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index cbd2db533d..9c9d327140 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -575,7 +575,7 @@ select jsonb_path_query('12.3', '$.number() * 2');
 -- Test .string()
 select jsonb_path_query('null', '$.string()');
 select jsonb_path_query('null', '$.string()', silent => true);
-select jsonb_path_query('[]', '$.string()');
+select jsonb_path_query('[2, true]', '$.string()');
 select jsonb_path_query('[]', 'strict $.string()');
 select jsonb_path_query('{}', '$.string()');
 select jsonb_path_query('[]', 'strict $.string()', silent => true);
-- 
2.45.2

