v5-0003-Implement-jsonpath-.boolean-and-.string-methods.patch

application/octet-stream

Filename: v5-0003-Implement-jsonpath-.boolean-and-.string-methods.patch
Type: application/octet-stream
Part: 0
Message: Re: More new SQL/JSON item methods

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch v5-0003
Subject: Implement jsonpath .boolean() and .string() methods
File+
doc/src/sgml/func.sgml 32 0
src/backend/catalog/sql_features.txt 2 2
src/backend/utils/adt/jsonpath.c 17 1
src/backend/utils/adt/jsonpath_exec.c 147 0
src/backend/utils/adt/jsonpath_gram.y 5 0
src/backend/utils/adt/jsonpath_scan.l 2 0
src/include/utils/jsonpath.h 2 0
src/test/regress/expected/jsonb_jsonpath.out 263 0
src/test/regress/expected/jsonpath.out 12 0
src/test/regress/sql/jsonb_jsonpath.sql 58 0
src/test/regress/sql/jsonpath.sql 2 0
From 6037788694e33b6a1de3413b98f8c6f5a5f19012 Mon Sep 17 00:00:00 2001
From: Jeevan Chalke <jeevan.chalke@enterprisedb.com>
Date: Wed, 10 Jan 2024 13:14:49 +0530
Subject: [PATCH v5 3/3] Implement jsonpath .boolean() and .string() methods

This commit implements jsonpath .boolean() and .string() methods.

.boolean() method converts the given JSON string, numeric, or boolean
value to the boolean type representation.  In the numeric case, only
integers are allowed, whereas we use the parse_bool() backend function
to convert string to a bool.

.string() method uses the datatype's out function to convert numeric
and various date/time types to the string representation.

Jeevan Chalke.
---
 doc/src/sgml/func.sgml                       |  32 ++++
 src/backend/catalog/sql_features.txt         |   4 +-
 src/backend/utils/adt/jsonpath.c             |  18 +-
 src/backend/utils/adt/jsonpath_exec.c        | 147 +++++++++++++++
 src/backend/utils/adt/jsonpath_gram.y        |   5 +
 src/backend/utils/adt/jsonpath_scan.l        |   2 +
 src/include/utils/jsonpath.h                 |   2 +
 src/test/regress/expected/jsonb_jsonpath.out | 263 +++++++++++++++++++++++++++
 src/test/regress/expected/jsonpath.out       |  12 ++
 src/test/regress/sql/jsonb_jsonpath.sql      |  58 ++++++
 src/test/regress/sql/jsonpath.sql            |   2 +
 11 files changed, 542 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 0080ee2..4ae97d5 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17676,6 +17676,38 @@ strict $.**.HR
 
       <row>
        <entry role="func_table_entry"><para role="func_signature">
+        <replaceable>value</replaceable> <literal>.</literal> <literal>boolean()</literal>
+        <returnvalue><replaceable>boolean</replaceable></returnvalue>
+       </para>
+       <para>
+        Boolean value converted from a JSON boolean, number, or string
+       </para>
+       <para>
+        <literal>jsonb_path_query_array('[1, "yes", false]', '$[*].boolean()')</literal>
+        <returnvalue>[true, true, false]</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <replaceable>value</replaceable> <literal>.</literal> <literal>string()</literal>
+        <returnvalue><replaceable>string</replaceable></returnvalue>
+       </para>
+       <para>
+        String value converted from a JSON boolean, number, string, or datetime
+       </para>
+       <para>
+        <literal>jsonb_path_query_array('[1.23, "xyz", false]', '$[*].string()')</literal>
+        <returnvalue>["1.23", "xyz", "false"]</returnvalue>
+       </para>
+       <para>
+        <literal>jsonb_path_query('"2023-08-15"', '$.datetime().string()')</literal>
+        <returnvalue>"2023-08-15"</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
         <replaceable>value</replaceable> <literal>.</literal> <literal>double()</literal>
         <returnvalue><replaceable>number</replaceable></returnvalue>
        </para>
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index aa80634..4085a2d 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -575,13 +575,13 @@ T862	SQL/JSON simplified accessor: wildcard member accessor			NO
 T863	SQL/JSON simplified accessor: single-quoted string literal as member accessor			NO	
 T864	SQL/JSON simplified accessor			NO	
 T865	SQL/JSON item method: bigint()			YES	
-T866	SQL/JSON item method: boolean()			NO	
+T866	SQL/JSON item method: boolean()			YES	
 T867	SQL/JSON item method: date()			YES	
 T868	SQL/JSON item method: decimal()			YES	
 T869	SQL/JSON item method: decimal() with precision and scale			YES	
 T870	SQL/JSON item method: integer()			YES	
 T871	SQL/JSON item method: number()			YES	
-T872	SQL/JSON item method: string()			NO	
+T872	SQL/JSON item method: string()			YES	
 T873	SQL/JSON item method: time()			YES	
 T874	SQL/JSON item method: time_tz()			YES	
 T875	SQL/JSON item method: time precision			YES	
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index ba9537e..457d4fc 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -453,6 +453,8 @@ flattenJsonPathParseItem(StringInfo buf, int *result, struct Node *escontext,
 		case jpiBigint:
 		case jpiInteger:
 		case jpiDate:
+		case jpiBoolean:
+		case jpiStringFunc:
 			break;
 		default:
 			elog(ERROR, "unrecognized jsonpath item type: %d", item->type);
@@ -814,6 +816,12 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
 			}
 			appendStringInfoChar(buf, ')');
 			break;
+		case jpiBoolean:
+			appendStringInfoString(buf, ".boolean()");
+			break;
+		case jpiStringFunc:
+			appendStringInfoString(buf, ".string()");
+			break;
 		default:
 			elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
 	}
@@ -893,6 +901,10 @@ jspOperationName(JsonPathItemType type)
 			return "timestamp";
 		case jpiTimestampTz:
 			return "timestamp_tz";
+		case jpiBoolean:
+			return "boolean";
+		case jpiStringFunc:
+			return "string";
 		default:
 			elog(ERROR, "unrecognized jsonpath item type: %d", type);
 			return NULL;
@@ -993,6 +1005,8 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
 		case jpiBigint:
 		case jpiInteger:
 		case jpiDate:
+		case jpiBoolean:
+		case jpiStringFunc:
 			break;
 		case jpiString:
 		case jpiKey:
@@ -1127,7 +1141,9 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
 			   v->type == jpiTime ||
 			   v->type == jpiTimeTz ||
 			   v->type == jpiTimestamp ||
-			   v->type == jpiTimestampTz);
+			   v->type == jpiTimestampTz ||
+			   v->type == jpiBoolean ||
+			   v->type == jpiStringFunc);
 
 		if (a)
 			jspInitByBuffer(a, v->base, v->nextPos);
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index aa154f3..c024ff7 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -1381,6 +1381,153 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
 			}
 			break;
 
+		case jpiBoolean:
+			{
+				JsonbValue	jbv;
+				bool		bval;
+
+				if (unwrap && JsonbType(jb) == jbvArray)
+					return executeItemUnwrapTargetArray(cxt, jsp, jb, found,
+														false);
+
+				if (jb->type == jbvBool)
+				{
+					bval = jb->val.boolean;
+
+					res = jperOk;
+				}
+				else if (jb->type == jbvNumeric)
+				{
+					int			ival;
+					Datum		datum;
+					bool		noerr;
+					char	   *tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
+																		  NumericGetDatum(jb->val.numeric)));
+					ErrorSaveContext escontext = {T_ErrorSaveContext};
+
+					noerr = DirectInputFunctionCallSafe(int4in, tmp,
+														InvalidOid, -1,
+														(Node *) &escontext,
+														&datum);
+
+					if (!noerr || escontext.error_occurred)
+						RETURN_ERROR(ereport(ERROR,
+											 (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
+											  errmsg("numeric argument of jsonpath item method .%s() is out of range for type boolean",
+													 jspOperationName(jsp->type)))));
+
+					ival = DatumGetInt32(datum);
+					if (ival == 0)
+						bval = false;
+					else
+						bval = true;
+
+					res = jperOk;
+				}
+				else if (jb->type == jbvString)
+				{
+					/* cast string as boolean */
+					char	   *tmp = pnstrdup(jb->val.string.val,
+											   jb->val.string.len);
+
+					if (!parse_bool(tmp, &bval))
+						RETURN_ERROR(ereport(ERROR,
+											 (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
+											  errmsg("string argument of jsonpath item method .%s() is not a valid representation of a boolean",
+													 jspOperationName(jsp->type)))));
+
+					res = jperOk;
+				}
+
+				if (res == jperNotFound)
+					RETURN_ERROR(ereport(ERROR,
+										 (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
+										  errmsg("jsonpath item method .%s() can only be applied to a bool, string, or numeric value",
+												 jspOperationName(jsp->type)))));
+
+				jb = &jbv;
+				jb->type = jbvBool;
+				jb->val.boolean = bval;
+
+				res = executeNextItem(cxt, jsp, NULL, jb, found, true);
+			}
+			break;
+
+		case jpiStringFunc:
+			{
+				JsonbValue	jbv;
+				char	   *tmp = NULL;
+
+				switch (JsonbType(jb))
+				{
+					case jbvString:
+						/*
+						 * Value is not necessarily null-terminated, so we do
+						 * pnstrdup() here.
+						 */
+						tmp = pnstrdup(jb->val.string.val,
+									   jb->val.string.len);
+						break;
+					case jbvNumeric:
+						tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
+																  NumericGetDatum(jb->val.numeric)));
+						break;
+					case jbvBool:
+						tmp = (jb->val.boolean) ? "true" : "false";
+						break;
+					case jbvDatetime:
+						{
+							switch (jb->val.datetime.typid)
+							{
+								case DATEOID:
+									tmp = DatumGetCString(DirectFunctionCall1(date_out,
+																			  jb->val.datetime.value));
+									break;
+								case TIMEOID:
+									tmp = DatumGetCString(DirectFunctionCall1(time_out,
+																			  jb->val.datetime.value));
+									break;
+								case TIMETZOID:
+									tmp = DatumGetCString(DirectFunctionCall1(timetz_out,
+																			  jb->val.datetime.value));
+									break;
+								case TIMESTAMPOID:
+									tmp = DatumGetCString(DirectFunctionCall1(timestamp_out,
+																			  jb->val.datetime.value));
+									break;
+								case TIMESTAMPTZOID:
+									tmp = DatumGetCString(DirectFunctionCall1(timestamptz_out,
+																			  jb->val.datetime.value));
+									break;
+								default:
+									elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u",
+										 jb->val.datetime.typid);
+							}
+						}
+						break;
+					case jbvNull:
+					case jbvArray:
+					case jbvObject:
+					case jbvBinary:
+						RETURN_ERROR(ereport(ERROR,
+											 (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
+											  errmsg("jsonpath item method .%s() can only be applied to a bool, string, numeric, or datetime value",
+													 jspOperationName(jsp->type)))));
+						break;
+				}
+
+				res = jperOk;
+
+				jb = &jbv;
+				Assert(tmp != NULL);		/* We must have set tmp above */
+				jb->val.string.val = (jb->type == jbvString) ? tmp : pstrdup(tmp);
+				jb->val.string.len = strlen(jb->val.string.val);
+				jb->type = jbvString;
+
+				res = executeNextItem(cxt, jsp, NULL, jb, found, true);
+			}
+			break;
+
 		default:
 			elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
 	}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 79c6371..3054762 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -83,6 +83,7 @@ static bool makeItemLikeRegex(JsonPathParseItem *expr,
 %token	<str>		ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P KEYVALUE_P
 %token	<str>		NUMBER_P DECIMAL_P BIGINT_P INTEGER_P
 %token	<str>		DATETIME_P DATE_P TIME_P TIME_TZ_P TIMESTAMP_P TIMESTAMP_TZ_P
+%token	<str>		BOOLEAN_P STRINGFUNC_P
 
 %type	<result>	result
 
@@ -356,6 +357,8 @@ key_name:
 	| TIME_TZ_P
 	| TIMESTAMP_P
 	| TIMESTAMP_TZ_P
+	| BOOLEAN_P
+	| STRINGFUNC_P
 	;
 
 method:
@@ -370,6 +373,8 @@ method:
 	| BIGINT_P						{ $$ = jpiBigint; }
 	| INTEGER_P						{ $$ = jpiInteger; }
 	| DATE_P						{ $$ = jpiDate; }
+	| BOOLEAN_P						{ $$ = jpiBoolean; }
+	| STRINGFUNC_P					{ $$ = jpiStringFunc; }
 	;
 %%
 
diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
index d3e9401..7acda77 100644
--- a/src/backend/utils/adt/jsonpath_scan.l
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -418,6 +418,8 @@ static const JsonPathKeyword keywords[] = {
 	{ 6, false,	NUMBER_P,	"number"},
 	{ 6, false,	STARTS_P,	"starts"},
 	{ 6, false,	STRICT_P,	"strict"},
+	{ 6, false,	STRINGFUNC_P, "string"},
+	{ 7, false,	BOOLEAN_P,	"boolean"},
 	{ 7, false,	CEILING_P,	"ceiling"},
 	{ 7, false,	DECIMAL_P,	"decimal"},
 	{ 7, false,	INTEGER_P,	"integer"},
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index d0c88d5..d85d4e4 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -111,6 +111,8 @@ typedef enum JsonPathItemType
 	jpiTimeTz,					/* .time_tz() item method */
 	jpiTimestamp,				/* .timestamp() item method */
 	jpiTimestampTz,				/* .timestamp_tz() item method */
+	jpiBoolean,					/* .boolean() item method */
+	jpiStringFunc,				/* .string() item method */
 } JsonPathItemType;
 
 /* XQuery regex mode flags for LIKE_REGEX predicate */
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index b77289c..67f47ca 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -1348,6 +1348,269 @@ select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'l
  1
 (9 rows)
 
+select jsonb_path_query('null', '$.boolean()');
+ERROR:  jsonpath item method .boolean() can only be applied to a bool, string, or numeric value
+select jsonb_path_query('null', '$.boolean()', silent => true);
+ jsonb_path_query 
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', '$.boolean()');
+ jsonb_path_query 
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $.boolean()');
+ERROR:  jsonpath item method .boolean() can only be applied to a bool, string, or numeric value
+select jsonb_path_query('{}', '$.boolean()');
+ERROR:  jsonpath item method .boolean() can only be applied to a bool, string, or numeric value
+select jsonb_path_query('[]', 'strict $.boolean()', silent => true);
+ jsonb_path_query 
+------------------
+(0 rows)
+
+select jsonb_path_query('{}', '$.boolean()', silent => true);
+ jsonb_path_query 
+------------------
+(0 rows)
+
+select jsonb_path_query('1.23', '$.boolean()');
+ERROR:  numeric argument of jsonpath item method .boolean() is out of range for type boolean
+select jsonb_path_query('"1.23"', '$.boolean()');
+ERROR:  string argument of jsonpath item method .boolean() is not a valid representation of a boolean
+select jsonb_path_query('"1.23aaa"', '$.boolean()');
+ERROR:  string argument of jsonpath item method .boolean() is not a valid representation of a boolean
+select jsonb_path_query('1e1000', '$.boolean()');
+ERROR:  numeric argument of jsonpath item method .boolean() is out of range for type boolean
+select jsonb_path_query('"nan"', '$.boolean()');
+ERROR:  string argument of jsonpath item method .boolean() is not a valid representation of a boolean
+select jsonb_path_query('"NaN"', '$.boolean()');
+ERROR:  string argument of jsonpath item method .boolean() is not a valid representation of a boolean
+select jsonb_path_query('"inf"', '$.boolean()');
+ERROR:  string argument of jsonpath item method .boolean() is not a valid representation of a boolean
+select jsonb_path_query('"-inf"', '$.boolean()');
+ERROR:  string argument of jsonpath item method .boolean() is not a valid representation of a boolean
+select jsonb_path_query('"inf"', '$.boolean()', silent => true);
+ jsonb_path_query 
+------------------
+(0 rows)
+
+select jsonb_path_query('"-inf"', '$.boolean()', silent => true);
+ jsonb_path_query 
+------------------
+(0 rows)
+
+select jsonb_path_query('"100"', '$.boolean()');
+ERROR:  string argument of jsonpath item method .boolean() is not a valid representation of a boolean
+select jsonb_path_query('true', '$.boolean()');
+ jsonb_path_query 
+------------------
+ true
+(1 row)
+
+select jsonb_path_query('false', '$.boolean()');
+ jsonb_path_query 
+------------------
+ false
+(1 row)
+
+select jsonb_path_query('1', '$.boolean()');
+ jsonb_path_query 
+------------------
+ true
+(1 row)
+
+select jsonb_path_query('0', '$.boolean()');
+ jsonb_path_query 
+------------------
+ false
+(1 row)
+
+select jsonb_path_query('-1', '$.boolean()');
+ jsonb_path_query 
+------------------
+ true
+(1 row)
+
+select jsonb_path_query('100', '$.boolean()');
+ jsonb_path_query 
+------------------
+ true
+(1 row)
+
+select jsonb_path_query('"1"', '$.boolean()');
+ jsonb_path_query 
+------------------
+ true
+(1 row)
+
+select jsonb_path_query('"0"', '$.boolean()');
+ jsonb_path_query 
+------------------
+ false
+(1 row)
+
+select jsonb_path_query('"true"', '$.boolean()');
+ jsonb_path_query 
+------------------
+ true
+(1 row)
+
+select jsonb_path_query('"false"', '$.boolean()');
+ jsonb_path_query 
+------------------
+ false
+(1 row)
+
+select jsonb_path_query('"TRUE"', '$.boolean()');
+ jsonb_path_query 
+------------------
+ true
+(1 row)
+
+select jsonb_path_query('"FALSE"', '$.boolean()');
+ jsonb_path_query 
+------------------
+ false
+(1 row)
+
+select jsonb_path_query('"yes"', '$.boolean()');
+ jsonb_path_query 
+------------------
+ true
+(1 row)
+
+select jsonb_path_query('"NO"', '$.boolean()');
+ jsonb_path_query 
+------------------
+ false
+(1 row)
+
+select jsonb_path_query('"T"', '$.boolean()');
+ jsonb_path_query 
+------------------
+ true
+(1 row)
+
+select jsonb_path_query('"f"', '$.boolean()');
+ jsonb_path_query 
+------------------
+ false
+(1 row)
+
+select jsonb_path_query('"y"', '$.boolean()');
+ jsonb_path_query 
+------------------
+ true
+(1 row)
+
+select jsonb_path_query('"N"', '$.boolean()');
+ jsonb_path_query 
+------------------
+ false
+(1 row)
+
+select jsonb_path_query('true', '$.boolean().type()');
+ jsonb_path_query 
+------------------
+ "boolean"
+(1 row)
+
+select jsonb_path_query('123', '$.boolean().type()');
+ jsonb_path_query 
+------------------
+ "boolean"
+(1 row)
+
+select jsonb_path_query('"Yes"', '$.boolean().type()');
+ jsonb_path_query 
+------------------
+ "boolean"
+(1 row)
+
+select jsonb_path_query_array('[1, "yes", false]', '$[*].boolean()');
+ jsonb_path_query_array 
+------------------------
+ [true, true, false]
+(1 row)
+
+select jsonb_path_query('null', '$.string()');
+ERROR:  jsonpath item method .string() can only be applied to a bool, string, numeric, or datetime value
+select jsonb_path_query('null', '$.string()', silent => true);
+ jsonb_path_query 
+------------------
+(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('[]', 'strict $.string()');
+ERROR:  jsonpath item method .string() can only be applied to a bool, string, numeric, or datetime value
+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('[]', 'strict $.string()', silent => true);
+ jsonb_path_query 
+------------------
+(0 rows)
+
+select jsonb_path_query('{}', '$.string()', silent => true);
+ jsonb_path_query 
+------------------
+(0 rows)
+
+select jsonb_path_query('1.23', '$.string()');
+ jsonb_path_query 
+------------------
+ "1.23"
+(1 row)
+
+select jsonb_path_query('"1.23"', '$.string()');
+ jsonb_path_query 
+------------------
+ "1.23"
+(1 row)
+
+select jsonb_path_query('"1.23aaa"', '$.string()');
+ jsonb_path_query 
+------------------
+ "1.23aaa"
+(1 row)
+
+select jsonb_path_query('1234', '$.string()');
+ jsonb_path_query 
+------------------
+ "1234"
+(1 row)
+
+select jsonb_path_query('true', '$.string()');
+ jsonb_path_query 
+------------------
+ "true"
+(1 row)
+
+select jsonb_path_query('1234', '$.string().type()');
+ jsonb_path_query 
+------------------
+ "string"
+(1 row)
+
+select jsonb_path_query('"2023-08-15 12:34:56 +5:30"', '$.timestamp().string()');
+      jsonb_path_query      
+----------------------------
+ "Tue Aug 15 00:04:56 2023"
+(1 row)
+
+select jsonb_path_query_array('[1.23, "yes", false]', '$[*].string()');
+  jsonb_path_query_array  
+--------------------------
+ ["1.23", "yes", "false"]
+(1 row)
+
+select jsonb_path_query_array('[1.23, "yes", false]', '$[*].string().type()');
+     jsonb_path_query_array     
+--------------------------------
+ ["string", "string", "string"]
+(1 row)
+
 select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].abs()');
  jsonb_path_query 
 ------------------
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index b47b2e9..6b16cda 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -381,6 +381,18 @@ select 'true.type()'::jsonpath;
  true.type()
 (1 row)
 
+select '$.boolean()'::jsonpath;
+  jsonpath   
+-------------
+ $.boolean()
+(1 row)
+
+select '$.string()'::jsonpath;
+  jsonpath  
+------------
+ $.string()
+(1 row)
+
 select '$.double().floor().ceiling().abs()'::jsonpath;
               jsonpath              
 ------------------------------------
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index eb96c3b..f4b9915 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -283,6 +283,64 @@ select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 's
 select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()', silent => true);
 select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()');
 
+select jsonb_path_query('null', '$.boolean()');
+select jsonb_path_query('null', '$.boolean()', silent => true);
+select jsonb_path_query('[]', '$.boolean()');
+select jsonb_path_query('[]', 'strict $.boolean()');
+select jsonb_path_query('{}', '$.boolean()');
+select jsonb_path_query('[]', 'strict $.boolean()', silent => true);
+select jsonb_path_query('{}', '$.boolean()', silent => true);
+select jsonb_path_query('1.23', '$.boolean()');
+select jsonb_path_query('"1.23"', '$.boolean()');
+select jsonb_path_query('"1.23aaa"', '$.boolean()');
+select jsonb_path_query('1e1000', '$.boolean()');
+select jsonb_path_query('"nan"', '$.boolean()');
+select jsonb_path_query('"NaN"', '$.boolean()');
+select jsonb_path_query('"inf"', '$.boolean()');
+select jsonb_path_query('"-inf"', '$.boolean()');
+select jsonb_path_query('"inf"', '$.boolean()', silent => true);
+select jsonb_path_query('"-inf"', '$.boolean()', silent => true);
+select jsonb_path_query('"100"', '$.boolean()');
+select jsonb_path_query('true', '$.boolean()');
+select jsonb_path_query('false', '$.boolean()');
+select jsonb_path_query('1', '$.boolean()');
+select jsonb_path_query('0', '$.boolean()');
+select jsonb_path_query('-1', '$.boolean()');
+select jsonb_path_query('100', '$.boolean()');
+select jsonb_path_query('"1"', '$.boolean()');
+select jsonb_path_query('"0"', '$.boolean()');
+select jsonb_path_query('"true"', '$.boolean()');
+select jsonb_path_query('"false"', '$.boolean()');
+select jsonb_path_query('"TRUE"', '$.boolean()');
+select jsonb_path_query('"FALSE"', '$.boolean()');
+select jsonb_path_query('"yes"', '$.boolean()');
+select jsonb_path_query('"NO"', '$.boolean()');
+select jsonb_path_query('"T"', '$.boolean()');
+select jsonb_path_query('"f"', '$.boolean()');
+select jsonb_path_query('"y"', '$.boolean()');
+select jsonb_path_query('"N"', '$.boolean()');
+select jsonb_path_query('true', '$.boolean().type()');
+select jsonb_path_query('123', '$.boolean().type()');
+select jsonb_path_query('"Yes"', '$.boolean().type()');
+select jsonb_path_query_array('[1, "yes", false]', '$[*].boolean()');
+
+select jsonb_path_query('null', '$.string()');
+select jsonb_path_query('null', '$.string()', silent => true);
+select jsonb_path_query('[]', '$.string()');
+select jsonb_path_query('[]', 'strict $.string()');
+select jsonb_path_query('{}', '$.string()');
+select jsonb_path_query('[]', 'strict $.string()', silent => true);
+select jsonb_path_query('{}', '$.string()', silent => true);
+select jsonb_path_query('1.23', '$.string()');
+select jsonb_path_query('"1.23"', '$.string()');
+select jsonb_path_query('"1.23aaa"', '$.string()');
+select jsonb_path_query('1234', '$.string()');
+select jsonb_path_query('true', '$.string()');
+select jsonb_path_query('1234', '$.string().type()');
+select jsonb_path_query('"2023-08-15 12:34:56 +5:30"', '$.timestamp().string()');
+select jsonb_path_query_array('[1.23, "yes", false]', '$[*].string()');
+select jsonb_path_query_array('[1.23, "yes", false]', '$[*].string().type()');
+
 select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].abs()');
 select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].floor()');
 select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling()');
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index 44275c9..8f07b71 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -69,6 +69,8 @@ select '(1).type()'::jsonpath;
 select '1.2.type()'::jsonpath;
 select '"aaa".type()'::jsonpath;
 select 'true.type()'::jsonpath;
+select '$.boolean()'::jsonpath;
+select '$.string()'::jsonpath;
 select '$.double().floor().ceiling().abs()'::jsonpath;
 select '$.bigint().integer().number().decimal()'::jsonpath;
 select '$.decimal(4,2)'::jsonpath;
-- 
1.8.3.1