Thread

  1. [PATCH v13 2/2] Add additional jsonpath string methods

    Florents Tselai <florents.tselai@gmail.com> — 2025-07-11T18:04:28Z

    Add the following jsonpath methods:
    
    *   l/r/btrim()
    *   lower(), upper()
    *   initcap()
    *   replace()
    *   split_part()
    
    Each simply dispatches to the standard string processing functions.
    These depend on the locale, but since it's set at `initdb`, they can be
    considered immutable and therefore allowed in any jsonpath expression.
    ---
     doc/src/sgml/func/func-json.sgml             | 140 +++++++
     src/backend/utils/adt/jsonpath.c             | 117 +++++-
     src/backend/utils/adt/jsonpath_exec.c        | 207 ++++++++++
     src/backend/utils/adt/jsonpath_gram.y        |  28 +-
     src/backend/utils/adt/jsonpath_scan.l        |   8 +
     src/include/utils/jsonpath.h                 |   8 +
     src/test/regress/expected/jsonb_jsonpath.out | 399 +++++++++++++++++++
     src/test/regress/expected/jsonpath.out       | 137 +++++++
     src/test/regress/sql/jsonb_jsonpath.sql      | 109 +++++
     src/test/regress/sql/jsonpath.sql            |  31 ++
     10 files changed, 1179 insertions(+), 5 deletions(-)
    
    diff --git a/doc/src/sgml/func/func-json.sgml b/doc/src/sgml/func/func-json.sgml
    index b9316ba0ee5..49031820c4b 100644
    --- a/doc/src/sgml/func/func-json.sgml
    +++ b/doc/src/sgml/func/func-json.sgml
    @@ -2781,6 +2781,146 @@ ERROR:  jsonpath member accessor can only be applied to an object
             <returnvalue>[{"id": 0, "key": "x", "value": "20"}, {"id": 0, "key": "y", "value": 32}]</returnvalue>
            </para></entry>
           </row>
    +
    +      <row>
    +       <entry role="func_table_entry"><para role="func_signature">
    +        <replaceable>string</replaceable> <literal>.</literal> <literal>lower()</literal>
    +        <returnvalue><replaceable>string</replaceable></returnvalue>
    +       </para>
    +       <para>
    +        String converted to all lower case according to the rules of the database's locale.
    +       </para>
    +       <para>
    +        <literal>jsonb_path_query('"TOM"', '$.lower()')</literal>
    +        <returnvalue>"tom"</returnvalue>
    +       </para></entry>
    +      </row>
    +
    +      <row>
    +       <entry role="func_table_entry"><para role="func_signature">
    +        <replaceable>string</replaceable> <literal>.</literal> <literal>upper()</literal>
    +        <returnvalue><replaceable>string</replaceable></returnvalue>
    +       </para>
    +       <para>
    +        String converted to all upper case according to the rules of the database's locale.
    +       </para>
    +       <para>
    +        <literal>jsonb_path_query('"tom"', '$.upper()')</literal>
    +        <returnvalue>"TOM"</returnvalue>
    +       </para></entry>
    +      </row>
    +
    +      <row>
    +       <entry role="func_table_entry"><para role="func_signature">
    +        <replaceable>string</replaceable> <literal>.</literal> <literal>initcap()</literal>
    +        <returnvalue><replaceable>string</replaceable></returnvalue>
    +       </para>
    +       <para>
    +        String with the first letter of each word converted to upper case
    +        according to the rules of the database's locale. Words are sequences
    +        of alphanumeric characters separated by non-alphanumeric characters.
    +       </para>
    +       <para>
    +        <literal>jsonb_path_query('"hi THOMAS"', '$.initcap()')</literal>
    +        <returnvalue>"Hi Thomas"</returnvalue>
    +       </para></entry>
    +      </row>
    +
    +      <row>
    +       <entry role="func_table_entry"><para role="func_signature">
    +        <replaceable>string</replaceable> <literal>.</literal> <literal>replace(<replaceable>from</replaceable>, <replaceable>to</replaceable>)</literal>
    +        <returnvalue><replaceable>string</replaceable></returnvalue>
    +       </para>
    +       <para>
    +        String with all occurrences of substring from replaced with substring to.
    +       </para>
    +       <para>
    +        <literal>jsonb_path_query('"abcdefabcdef"', '$.replace("cd", "XX")')</literal>
    +        <returnvalue>"abXXefabXXef"</returnvalue>
    +       </para></entry>
    +      </row>
    +
    +      <row>
    +       <entry role="func_table_entry"><para role="func_signature">
    +        <replaceable>string</replaceable> <literal>.</literal> <literal>split_part(<replaceable>delimiter</replaceable>, <replaceable>n</replaceable>)</literal>
    +        <returnvalue><replaceable>string</replaceable></returnvalue>
    +       </para>
    +       <para>
    +        String split at occurrences of <replaceable>delimiter</replaceable>
    +        and returns the <replaceable>n</replaceable>'th field (counting from
    +        one) or, when <replaceable>n</replaceable> is negative, returns the
    +        |<replaceable>n</replaceable>|'th-from-last field.
    +       </para>
    +       <para>
    +        <literal>jsonb_path_query('"abc~@~def~@~ghi"', '$.split_part("~@~", 2)')</literal>
    +        <returnvalue>"def"</returnvalue>
    +       </para>
    +       <para>
    +        <literal>jsonb_path_query('"abc,def,ghi,jkl"', '$.split_part(",", 2)')</literal>
    +        <returnvalue>"ghi"</returnvalue>
    +       </para></entry>
    +      </row>
    +
    +      <row>
    +       <entry role="func_table_entry"><para role="func_signature">
    +        <replaceable>string</replaceable> <literal>.</literal> <literal>ltrim(<replaceable>characters</replaceable>)</literal>
    +        <returnvalue><replaceable>string</replaceable></returnvalue>
    +       </para>
    +       <para>
    +        String with the longest string containing only spaces or the
    +        characters in <replaceable>characters</replaceable> removed from the
    +        start of <replaceable>string</replaceable>
    +       </para>
    +       <para>
    +        <literal> jsonb_path_query('"  hello"', '$.ltrim()')</literal>
    +        <returnvalue>"hello"</returnvalue>
    +       </para>
    +       <para>
    +        <literal>jsonb_path_query('"zzzytest"', '$.ltrim("xyz")')</literal>
    +        <returnvalue>"test"</returnvalue>
    +       </para></entry>
    +      </row>
    +
    +      <row>
    +       <entry role="func_table_entry"><para role="func_signature">
    +        <replaceable>string</replaceable> <literal>.</literal> <literal>rtrim([ <replaceable>characters</replaceable> ])</literal>
    +        <returnvalue><replaceable>string</replaceable></returnvalue>
    +       </para>
    +       <para>
    +        String with the longest string containing only spaces or the
    +        characters in <replaceable>characters</replaceable> removed from the
    +        end of <replaceable>string</replaceable>
    +       </para>
    +       <para>
    +        <literal>jsonb_path_query('"hello  "', '$.rtrim()')</literal>
    +        <returnvalue>"hello"</returnvalue>
    +       </para>
    +       <para>
    +        <literal>jsonb_path_query('"testxxzx"', '$.rtrim("xyz")')</literal>
    +        <returnvalue>"test"</returnvalue>
    +       </para></entry>
    +      </row>
    +
    +      <row>
    +       <entry role="func_table_entry"><para role="func_signature">
    +        <replaceable>string</replaceable> <literal>.</literal> <literal>btrim([ <replaceable>characters</replaceable> ])</literal>
    +        <returnvalue><replaceable>string</replaceable></returnvalue>
    +       </para>
    +       <para>
    +        String with the longest string containing only spaces or the
    +        characters in <replaceable>characters</replaceable> removed from the
    +        start and end of <replaceable>string</replaceable>
    +       </para>
    +       <para>
    +        <literal>jsonb_path_query('"  hello  "', '$.btrim()')</literal>
    +        <returnvalue>"hello"</returnvalue>
    +       </para>
    +       <para>
    +        <literal>jsonb_path_query('"xyxtrimyyx"', '$.btrim("xyz")')</literal>
    +        <returnvalue>"trim"</returnvalue>
    +       </para></entry>
    +      </row>
    +
          </tbody>
         </tgroup>
        </table>
    diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
    index 762f7e8a09d..f3f2119a4a8 100644
    --- a/src/backend/utils/adt/jsonpath.c
    +++ b/src/backend/utils/adt/jsonpath.c
    @@ -298,6 +298,8 @@ flattenJsonPathParseItem(StringInfo buf, int *result, struct Node *escontext,
     		case jpiMod:
     		case jpiStartsWith:
     		case jpiDecimal:
    +		case jpiStrReplace:
    +		case jpiStrSplitPart:
     			{
     				/*
     				 * First, reserve place for left/right arg's positions, then
    @@ -362,6 +364,9 @@ flattenJsonPathParseItem(StringInfo buf, int *result, struct Node *escontext,
     		case jpiTimeTz:
     		case jpiTimestamp:
     		case jpiTimestampTz:
    +		case jpiStrLtrim:
    +		case jpiStrRtrim:
    +		case jpiStrBtrim:
     			{
     				int32		arg = reserveSpaceForItemPointer(buf);
     
    @@ -457,6 +462,9 @@ flattenJsonPathParseItem(StringInfo buf, int *result, struct Node *escontext,
     		case jpiInteger:
     		case jpiNumber:
     		case jpiStringFunc:
    +		case jpiStrLower:
    +		case jpiStrUpper:
    +		case jpiStrInitcap:
     			break;
     		default:
     			elog(ERROR, "unrecognized jsonpath item type: %d", item->type);
    @@ -831,6 +839,60 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
     			}
     			appendStringInfoChar(buf, ')');
     			break;
    +		case jpiStrReplace:
    +			appendStringInfoString(buf, ".replace(");
    +			jspGetLeftArg(v, &elem);
    +			printJsonPathItem(buf, &elem, false, false);
    +			appendStringInfoChar(buf, ',');
    +			jspGetRightArg(v, &elem);
    +			printJsonPathItem(buf, &elem, false, false);
    +			appendStringInfoChar(buf, ')');
    +			break;
    +		case jpiStrSplitPart:
    +			appendStringInfoString(buf, ".split_part(");
    +			jspGetLeftArg(v, &elem);
    +			printJsonPathItem(buf, &elem, false, false);
    +			appendStringInfoChar(buf, ',');
    +			jspGetRightArg(v, &elem);
    +			printJsonPathItem(buf, &elem, false, false);
    +			appendStringInfoChar(buf, ')');
    +			break;
    +		case jpiStrLower:
    +			appendStringInfoString(buf, ".lower()");
    +			break;
    +		case jpiStrUpper:
    +			appendStringInfoString(buf, ".upper()");
    +			break;
    +		case jpiStrInitcap:
    +			appendStringInfoString(buf, ".initcap()");
    +			break;
    +		case jpiStrLtrim:
    +			appendStringInfoString(buf, ".ltrim(");
    +			if (v->content.arg)
    +			{
    +				jspGetArg(v, &elem);
    +				printJsonPathItem(buf, &elem, false, false);
    +			}
    +			appendStringInfoChar(buf, ')');
    +			break;
    +		case jpiStrRtrim:
    +			appendStringInfoString(buf, ".rtrim(");
    +			if (v->content.arg)
    +			{
    +				jspGetArg(v, &elem);
    +				printJsonPathItem(buf, &elem, false, false);
    +			}
    +			appendStringInfoChar(buf, ')');
    +			break;
    +		case jpiStrBtrim:
    +			appendStringInfoString(buf, ".btrim(");
    +			if (v->content.arg)
    +			{
    +				jspGetArg(v, &elem);
    +				printJsonPathItem(buf, &elem, false, false);
    +			}
    +			appendStringInfoChar(buf, ')');
    +			break;
     		default:
     			elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
     	}
    @@ -906,6 +968,12 @@ jspOperationName(JsonPathItemType type)
     			return "number";
     		case jpiStringFunc:
     			return "string";
    +		case jpiStrReplace:
    +			return "replace";
    +		case jpiStrLower:
    +			return "lower";
    +		case jpiStrUpper:
    +			return "upper";
     		case jpiTime:
     			return "time";
     		case jpiTimeTz:
    @@ -914,6 +982,16 @@ jspOperationName(JsonPathItemType type)
     			return "timestamp";
     		case jpiTimestampTz:
     			return "timestamp_tz";
    +		case jpiStrLtrim:
    +			return "ltrim";
    +		case jpiStrRtrim:
    +			return "rtrim";
    +		case jpiStrBtrim:
    +			return "btrim";
    +		case jpiStrInitcap:
    +			return "initcap";
    +		case jpiStrSplitPart:
    +			return "split_part";
     		default:
     			elog(ERROR, "unrecognized jsonpath item type: %d", type);
     			return NULL;
    @@ -1016,6 +1094,9 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
     		case jpiInteger:
     		case jpiNumber:
     		case jpiStringFunc:
    +		case jpiStrLower:
    +		case jpiStrUpper:
    +		case jpiStrInitcap:
     			break;
     		case jpiString:
     		case jpiKey:
    @@ -1041,6 +1122,8 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
     		case jpiMod:
     		case jpiStartsWith:
     		case jpiDecimal:
    +		case jpiStrReplace:
    +		case jpiStrSplitPart:
     			read_int32(v->content.args.left, base, pos);
     			read_int32(v->content.args.right, base, pos);
     			break;
    @@ -1055,6 +1138,9 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
     		case jpiTimeTz:
     		case jpiTimestamp:
     		case jpiTimestampTz:
    +		case jpiStrLtrim:
    +		case jpiStrRtrim:
    +		case jpiStrBtrim:
     			read_int32(v->content.arg, base, pos);
     			break;
     		case jpiIndexArray:
    @@ -1090,7 +1176,10 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a)
     		   v->type == jpiTime ||
     		   v->type == jpiTimeTz ||
     		   v->type == jpiTimestamp ||
    -		   v->type == jpiTimestampTz);
    +		   v->type == jpiTimestampTz ||
    +		   v->type == jpiStrLtrim ||
    +		   v->type == jpiStrRtrim ||
    +		   v->type == jpiStrBtrim);
     
     	jspInitByBuffer(a, v->base, v->content.arg);
     }
    @@ -1149,10 +1238,18 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
     			   v->type == jpiInteger ||
     			   v->type == jpiNumber ||
     			   v->type == jpiStringFunc ||
    +			   v->type == jpiStrReplace ||
    +			   v->type == jpiStrLower ||
    +			   v->type == jpiStrUpper ||
     			   v->type == jpiTime ||
     			   v->type == jpiTimeTz ||
     			   v->type == jpiTimestamp ||
    -			   v->type == jpiTimestampTz);
    +			   v->type == jpiTimestampTz ||
    +			   v->type == jpiStrLtrim ||
    +			   v->type == jpiStrRtrim ||
    +			   v->type == jpiStrBtrim ||
    +			   v->type == jpiStrInitcap ||
    +			   v->type == jpiStrSplitPart);
     
     		if (a)
     			jspInitByBuffer(a, v->base, v->nextPos);
    @@ -1179,7 +1276,9 @@ jspGetLeftArg(JsonPathItem *v, JsonPathItem *a)
     		   v->type == jpiDiv ||
     		   v->type == jpiMod ||
     		   v->type == jpiStartsWith ||
    -		   v->type == jpiDecimal);
    +		   v->type == jpiDecimal ||
    +		   v->type == jpiStrReplace ||
    +		   v->type == jpiStrSplitPart);
     
     	jspInitByBuffer(a, v->base, v->content.args.left);
     }
    @@ -1201,7 +1300,9 @@ jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
     		   v->type == jpiDiv ||
     		   v->type == jpiMod ||
     		   v->type == jpiStartsWith ||
    -		   v->type == jpiDecimal);
    +		   v->type == jpiDecimal ||
    +		   v->type == jpiStrReplace ||
    +		   v->type == jpiStrSplitPart);
     
     	jspInitByBuffer(a, v->base, v->content.args.right);
     }
    @@ -1501,6 +1602,14 @@ jspIsMutableWalker(JsonPathItem *jpi, struct JsonPathMutableContext *cxt)
     			case jpiInteger:
     			case jpiNumber:
     			case jpiStringFunc:
    +			case jpiStrReplace:
    +			case jpiStrLower:
    +			case jpiStrUpper:
    +			case jpiStrLtrim:
    +			case jpiStrRtrim:
    +			case jpiStrBtrim:
    +			case jpiStrInitcap:
    +			case jpiStrSplitPart:
     				status = jpdsNonDateTime;
     				break;
     
    diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
    index 8156695e97e..c22917796da 100644
    --- a/src/backend/utils/adt/jsonpath_exec.c
    +++ b/src/backend/utils/adt/jsonpath_exec.c
    @@ -302,6 +302,8 @@ static JsonPathExecResult executeNumericItemMethod(JsonPathExecContext *cxt,
     												   JsonValueList *found);
     static JsonPathExecResult executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
     												JsonbValue *jb, JsonValueList *found);
    +static JsonPathExecResult executeStringInternalMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
    +													  JsonbValue *jb, JsonValueList *found);
     static JsonPathExecResult executeKeyValueMethod(JsonPathExecContext *cxt,
     												JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
     static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt,
    @@ -1661,6 +1663,23 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
     			}
     			break;
     
    +		case jpiStrLtrim:
    +		case jpiStrLower:
    +		case jpiStrUpper:
    +		case jpiStrReplace:
    +		case jpiStrRtrim:
    +		case jpiStrBtrim:
    +		case jpiStrInitcap:
    +		case jpiStrSplitPart:
    +			{
    +				if (unwrap && JsonbType(jb) == jbvArray)
    +					return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
    +
    +				return executeStringInternalMethod(cxt, jsp, jb, found);
    +			}
    +			break;
    +
    +
     		default:
     			elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
     	}
    @@ -2794,6 +2813,194 @@ executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
     	return executeNextItem(cxt, jsp, &elem, jb, found, hasNext);
     }
     
    +/*
    + * Implementation of .upper(), lower() et. al. methods,
    + * that forward their actual implementation to internal functions.
    + */
    +static JsonPathExecResult
    +executeStringInternalMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
    +							JsonbValue *jb, JsonValueList *found)
    +{
    +	JsonbValue	jbvbuf;
    +	bool		hasNext;
    +	JsonPathExecResult res = jperNotFound;
    +	JsonPathItem elem;
    +	Datum		str;			/* Datum representation for the current string
    +								 * value. The first argument to internal
    +								 * functions */
    +	char	   *tmp = NULL;
    +	char	   *resStr = NULL;
    +
    +	Assert(jsp->type == jpiStrLower ||
    +		   jsp->type == jpiStrUpper ||
    +		   jsp->type == jpiStrReplace ||
    +		   jsp->type == jpiStrLtrim ||
    +		   jsp->type == jpiStrRtrim ||
    +		   jsp->type == jpiStrBtrim ||
    +		   jsp->type == jpiStrInitcap ||
    +		   jsp->type == jpiStrSplitPart);
    +
    +	if (!(jb = getScalar(jb, jbvString)))
    +		RETURN_ERROR(ereport(ERROR,
    +							 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
    +							  errmsg("jsonpath item method .%s() can only be applied to a string",
    +									 jspOperationName(jsp->type)))));
    +
    +	tmp = pnstrdup(jb->val.string.val, jb->val.string.len);
    +	str = CStringGetTextDatum(tmp);
    +
    +	/* Internal string functions that accept no arguments */
    +	switch (jsp->type)
    +	{
    +		case jpiStrLtrim:
    +		case jpiStrRtrim:
    +		case jpiStrBtrim:
    +			{
    +				char	   *characters_str;
    +				int			characters_len;
    +				PGFunction	func = NULL;
    +
    +				if (jsp->content.arg)
    +				{
    +					switch (jsp->type)
    +					{
    +						case jpiStrLtrim:
    +							func = ltrim;
    +							break;
    +						case jpiStrRtrim:
    +							func = rtrim;
    +							break;
    +						case jpiStrBtrim:
    +							func = btrim;
    +							break;
    +						default:;
    +					}
    +					jspGetArg(jsp, &elem);
    +					if (elem.type != jpiString)
    +						elog(ERROR, "invalid jsonpath item type for .%s() argument", jspOperationName(jsp->type));
    +
    +					characters_str = jspGetString(&elem, &characters_len);
    +					resStr = TextDatumGetCString(DirectFunctionCall2Coll(func,
    +																		 DEFAULT_COLLATION_OID, str,
    +																		 CStringGetTextDatum(characters_str)));
    +					break;
    +				}
    +
    +				switch (jsp->type)
    +				{
    +					case jpiStrLtrim:
    +						func = ltrim1;
    +						break;
    +					case jpiStrRtrim:
    +						func = rtrim1;
    +						break;
    +					case jpiStrBtrim:
    +						func = btrim1;
    +						break;
    +					default:;
    +				}
    +				resStr = TextDatumGetCString(DirectFunctionCall1Coll(func,
    +																	 DEFAULT_COLLATION_OID, str));
    +				break;
    +			}
    +
    +		case jpiStrLower:
    +			resStr = TextDatumGetCString(DirectFunctionCall1Coll(lower, DEFAULT_COLLATION_OID, str));
    +			break;
    +		case jpiStrUpper:
    +			resStr = TextDatumGetCString(DirectFunctionCall1Coll(upper, DEFAULT_COLLATION_OID, str));
    +			break;
    +		case jpiStrInitcap:
    +			resStr = TextDatumGetCString(DirectFunctionCall1Coll(initcap, DEFAULT_COLLATION_OID, str));
    +			break;
    +		case jpiStrReplace:
    +			{
    +				char	   *from_str,
    +						   *to_str;
    +				int			from_len,
    +							to_len;
    +
    +				jspGetLeftArg(jsp, &elem);
    +				if (elem.type != jpiString)
    +					elog(ERROR, "invalid jsonpath item type for .replace() from");
    +
    +				from_str = jspGetString(&elem, &from_len);
    +
    +				jspGetRightArg(jsp, &elem);
    +				if (elem.type != jpiString)
    +					elog(ERROR, "invalid jsonpath item type for .replace() to");
    +
    +				to_str = jspGetString(&elem, &to_len);
    +
    +				resStr = TextDatumGetCString(DirectFunctionCall3Coll(replace_text,
    +																	 C_COLLATION_OID,
    +																	 CStringGetTextDatum(tmp),
    +																	 CStringGetTextDatum(from_str),
    +																	 CStringGetTextDatum(to_str)));
    +				break;
    +			}
    +		case jpiStrSplitPart:
    +			{
    +				char	   *from_str;
    +				Numeric		n;
    +				int			from_len;
    +
    +				jspGetLeftArg(jsp, &elem);
    +				if (elem.type != jpiString)
    +					elog(ERROR, "invalid jsonpath item type for .split_part()");
    +
    +				from_str = jspGetString(&elem, &from_len);
    +
    +				jspGetRightArg(jsp, &elem);
    +				if (elem.type != jpiNumeric)
    +					elog(ERROR, "invalid jsonpath item type for .split_part()");
    +
    +				n = jspGetNumeric(&elem);
    +
    +				resStr = TextDatumGetCString(DirectFunctionCall3Coll(split_part,
    +																	 C_COLLATION_OID,
    +																	 CStringGetTextDatum(tmp),
    +																	 CStringGetTextDatum(from_str),
    +																	 DirectFunctionCall1(numeric_int4, NumericGetDatum(n))));
    +				break;
    +			}
    +		default:
    +			elog(ERROR, "unsupported jsonpath item type: %d", jsp->type);
    +	}
    +
    +	if (resStr)
    +		res = jperOk;
    +
    +	hasNext = jspGetNext(jsp, &elem);
    +
    +	if (!hasNext && !found)
    +		return res;
    +
    +	jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
    +
    +	/* Create the appropriate jb value to return */
    +	switch (jsp->type)
    +	{
    +			/* Cases for functions that return text */
    +		case jpiStrLower:
    +		case jpiStrUpper:
    +		case jpiStrReplace:
    +		case jpiStrLtrim:
    +		case jpiStrRtrim:
    +		case jpiStrBtrim:
    +		case jpiStrInitcap:
    +		case jpiStrSplitPart:
    +			jb->type = jbvString;
    +			jb->val.string.val = resStr;
    +			jb->val.string.len = strlen(jb->val.string.val);
    +		default:
    +			;
    +			/* cant' happen */
    +	}
    +
    +	return executeNextItem(cxt, jsp, &elem, jb, found, hasNext);
    +}
    +
     /*
      * Implementation of .keyvalue() method.
      *
    diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
    index 0b16cec18c4..53bf779e152 100644
    --- a/src/backend/utils/adt/jsonpath_gram.y
    +++ b/src/backend/utils/adt/jsonpath_gram.y
    @@ -86,6 +86,8 @@ static bool makeItemLikeRegex(JsonPathParseItem *expr,
     %token	<str>		DATETIME_P
     %token	<str>		BIGINT_P BOOLEAN_P DATE_P DECIMAL_P INTEGER_P NUMBER_P
     %token	<str>		STRINGFUNC_P TIME_P TIME_TZ_P TIMESTAMP_P TIMESTAMP_TZ_P
    +%token	<str>		STR_REPLACE_P STR_LOWER_P STR_UPPER_P STR_LTRIM_P STR_RTRIM_P STR_BTRIM_P
    +					STR_INITCAP_P STR_SPLIT_PART_P
     
     %type	<result>	result
     
    @@ -95,7 +97,7 @@ static bool makeItemLikeRegex(JsonPathParseItem *expr,
     					str_elem opt_str_arg int_elem
     					uint_elem opt_uint_arg
     
    -%type	<elems>		accessor_expr int_list opt_int_list
    +%type	<elems>		accessor_expr int_list opt_int_list str_int_args str_str_args
     
     %type	<indexs>	index_list
     
    @@ -278,6 +280,16 @@ accessor_op:
     		{ $$ = makeItemUnary(jpiTimestamp, $4); }
     	| '.' TIMESTAMP_TZ_P '(' opt_uint_arg ')'
     		{ $$ = makeItemUnary(jpiTimestampTz, $4); }
    +	| '.' STR_REPLACE_P '(' str_str_args ')'
    +		{ $$ = makeItemBinary(jpiStrReplace, linitial($4), lsecond($4)); }
    +	| '.' STR_SPLIT_PART_P '(' str_int_args ')'
    +		{ $$ = makeItemBinary(jpiStrSplitPart, linitial($4), lsecond($4)); }
    +	| '.' STR_LTRIM_P '(' opt_str_arg ')'
    +		{ $$ = makeItemUnary(jpiStrLtrim, $4); }
    +	| '.' STR_RTRIM_P '(' opt_str_arg ')'
    +		{ $$ = makeItemUnary(jpiStrRtrim, $4); }
    +	| '.' STR_BTRIM_P '(' opt_str_arg ')'
    +		{ $$ = makeItemUnary(jpiStrBtrim, $4); }
     	;
     
     int_elem:
    @@ -317,6 +329,14 @@ opt_str_arg:
     	| /* EMPTY */					{ $$ = NULL; }
     	;
     
    +str_int_args:
    +	str_elem ',' int_elem			{ $$ = list_make2($1, $3); }
    +	;
    +
    +str_str_args:
    +	str_elem ',' str_elem 			{ $$ = list_make2($1, $3); }
    +	;
    +
     key:
     	key_name						{ $$ = makeItemKey(&$1); }
     	;
    @@ -357,6 +377,9 @@ key_name:
     	| TIME_TZ_P
     	| TIMESTAMP_P
     	| TIMESTAMP_TZ_P
    +	| STR_LTRIM_P
    +	| STR_RTRIM_P
    +	| STR_BTRIM_P
     	;
     
     method:
    @@ -373,6 +396,9 @@ method:
     	| INTEGER_P						{ $$ = jpiInteger; }
     	| NUMBER_P						{ $$ = jpiNumber; }
     	| STRINGFUNC_P					{ $$ = jpiStringFunc; }
    +	| STR_LOWER_P					{ $$ = jpiStrLower; }
    +	| STR_UPPER_P					{ $$ = jpiStrUpper; }
    +	| STR_INITCAP_P					{ $$ = jpiStrInitcap; }
     	;
     %%
     
    diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
    index c7aab83eeb4..69c61f98500 100644
    --- a/src/backend/utils/adt/jsonpath_scan.l
    +++ b/src/backend/utils/adt/jsonpath_scan.l
    @@ -413,8 +413,13 @@ static const JsonPathKeyword keywords[] = {
     	{4, true, TRUE_P, "true"},
     	{4, false, TYPE_P, "type"},
     	{4, false, WITH_P, "with"},
    +	{5, false, STR_BTRIM_P, "btrim"},
     	{5, true, FALSE_P, "false"},
     	{5, false, FLOOR_P, "floor"},
    +	{5, false, STR_LOWER_P, "lower"},
    +	{5, false, STR_LTRIM_P, "ltrim"},
    +	{5, false, STR_RTRIM_P, "rtrim"},
    +	{5, false, STR_UPPER_P, "upper"},
     	{6, false, BIGINT_P, "bigint"},
     	{6, false, DOUBLE_P, "double"},
     	{6, false, EXISTS_P, "exists"},
    @@ -425,13 +430,16 @@ static const JsonPathKeyword keywords[] = {
     	{7, false, BOOLEAN_P, "boolean"},
     	{7, false, CEILING_P, "ceiling"},
     	{7, false, DECIMAL_P, "decimal"},
    +	{7, false, STR_INITCAP_P, "initcap"},
     	{7, false, INTEGER_P, "integer"},
    +	{7, false, STR_REPLACE_P, "replace"},
     	{7, false, TIME_TZ_P, "time_tz"},
     	{7, false, UNKNOWN_P, "unknown"},
     	{8, false, DATETIME_P, "datetime"},
     	{8, false, KEYVALUE_P, "keyvalue"},
     	{9, false, TIMESTAMP_P, "timestamp"},
     	{10, false, LIKE_REGEX_P, "like_regex"},
    +	{10,false, STR_SPLIT_PART_P, "split_part"},
     	{12, false, TIMESTAMP_TZ_P, "timestamp_tz"},
     };
     
    diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
    index 23a76d233e9..2d0c53f7b06 100644
    --- a/src/include/utils/jsonpath.h
    +++ b/src/include/utils/jsonpath.h
    @@ -115,6 +115,14 @@ typedef enum JsonPathItemType
     	jpiTimeTz,					/* .time_tz() item method */
     	jpiTimestamp,				/* .timestamp() item method */
     	jpiTimestampTz,				/* .timestamp_tz() item method */
    +	jpiStrReplace,				/* .replace() item method */
    +	jpiStrLower,				/* .lower() item method */
    +	jpiStrUpper,				/* .upper() item method */
    +	jpiStrLtrim,				/* .ltrim() item method */
    +	jpiStrRtrim,				/* .rtrim() item method */
    +	jpiStrBtrim,				/* .btrim() item method */
    +	jpiStrInitcap,				/* .initcap() item method */
    +	jpiStrSplitPart,			/* .split_part() 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 4bcd4e91a29..cea539717f9 100644
    --- a/src/test/regress/expected/jsonb_jsonpath.out
    +++ b/src/test/regress/expected/jsonb_jsonpath.out
    @@ -2723,6 +2723,405 @@ select jsonb_path_query('"2023-08-15 12:34:56"', '$.timestamp().string()');
     (1 row)
     
     rollback;
    +-- test .ltrim()
    +select jsonb_path_query('"   hello   "', '$.ltrim(" ")');
    + jsonb_path_query 
    +------------------
    + "hello   "
    +(1 row)
    +
    +select jsonb_path_query('"   hello   "', '$.ltrim(" ")');
    + jsonb_path_query 
    +------------------
    + "hello   "
    +(1 row)
    +
    +select jsonb_path_query('"   hello   "', '$.ltrim()');
    + jsonb_path_query 
    +------------------
    + "hello   "
    +(1 row)
    +
    +select jsonb_path_query('"zzzytest"', '$.ltrim("xyz")');
    + jsonb_path_query 
    +------------------
    + "test"
    +(1 row)
    +
    +select jsonb_path_query('null', '$.ltrim()');
    +ERROR:  jsonpath item method .ltrim() can only be applied to a string
    +select jsonb_path_query('null', '$.ltrim()', silent => true);
    + jsonb_path_query 
    +------------------
    +(0 rows)
    +
    +select jsonb_path_query('[]', '$.ltrim()');
    + jsonb_path_query 
    +------------------
    +(0 rows)
    +
    +select jsonb_path_query('[]', 'strict $.ltrim()');
    +ERROR:  jsonpath item method .ltrim() can only be applied to a string
    +select jsonb_path_query('{}', '$.ltrim()');
    +ERROR:  jsonpath item method .ltrim() can only be applied to a string
    +select jsonb_path_query('[]', 'strict $.ltrim()', silent => true);
    + jsonb_path_query 
    +------------------
    +(0 rows)
    +
    +select jsonb_path_query('{}', '$.ltrim()', silent => true);
    + jsonb_path_query 
    +------------------
    +(0 rows)
    +
    +select jsonb_path_query('1.23', '$.ltrim()');
    +ERROR:  jsonpath item method .ltrim() can only be applied to a string
    +select jsonb_path_query('"1.23"', '$.ltrim()');
    + jsonb_path_query 
    +------------------
    + "1.23"
    +(1 row)
    +
    +select jsonb_path_query('"1.23aaa"', '$.ltrim()');
    + jsonb_path_query 
    +------------------
    + "1.23aaa"
    +(1 row)
    +
    +select jsonb_path_query('1234', '$.ltrim()');
    +ERROR:  jsonpath item method .ltrim() can only be applied to a string
    +select jsonb_path_query('true', '$.ltrim()');
    +ERROR:  jsonpath item method .ltrim() can only be applied to a string
    +select jsonb_path_query('1234', '$.ltrim().type()');
    +ERROR:  jsonpath item method .ltrim() can only be applied to a string
    +select jsonb_path_query('[2, true]', '$.ltrim()');
    +ERROR:  jsonpath item method .ltrim() can only be applied to a string
    +select jsonb_path_query_array('["  maybe  ", "  yes", "  no"]', '$[*].ltrim()');
    +  jsonb_path_query_array  
    +--------------------------
    + ["maybe  ", "yes", "no"]
    +(1 row)
    +
    +select jsonb_path_query_array('["  maybe  ", "  yes", "  no"]', '$[*].ltrim().type()');
    +     jsonb_path_query_array     
    +--------------------------------
    + ["string", "string", "string"]
    +(1 row)
    +
    +-- test .rtrim()
    +select jsonb_path_query('"   hello   "', '$.rtrim(" ")');
    + jsonb_path_query 
    +------------------
    + "   hello"
    +(1 row)
    +
    +select jsonb_path_query('"testxxzx"', '$.rtrim("xyz")');
    + jsonb_path_query 
    +------------------
    + "test"
    +(1 row)
    +
    +select jsonb_path_query('"   hello   "', '$.rtrim()');
    + jsonb_path_query 
    +------------------
    + "   hello"
    +(1 row)
    +
    +select jsonb_path_query('"   hello   "', '$.rtrim()');
    + jsonb_path_query 
    +------------------
    + "   hello"
    +(1 row)
    +
    +-- test .btrim()
    +select jsonb_path_query('"   hello   "', '$.btrim(" ")');
    + jsonb_path_query 
    +------------------
    + "hello"
    +(1 row)
    +
    +select jsonb_path_query('"xyxtrimyyx"', '$.btrim("xyz")');
    + jsonb_path_query 
    +------------------
    + "trim"
    +(1 row)
    +
    +select jsonb_path_query('"   hello   "', '$.btrim()');
    + jsonb_path_query 
    +------------------
    + "hello"
    +(1 row)
    +
    +select jsonb_path_query('"   hello   "', '$.btrim()');
    + jsonb_path_query 
    +------------------
    + "hello"
    +(1 row)
    +
    +-- test .lower()
    +select jsonb_path_query('null', '$.lower()');
    +ERROR:  jsonpath item method .lower() can only be applied to a string
    +select jsonb_path_query('null', '$.lower()', silent => true);
    + jsonb_path_query 
    +------------------
    +(0 rows)
    +
    +select jsonb_path_query('[]', '$.lower()');
    + jsonb_path_query 
    +------------------
    +(0 rows)
    +
    +select jsonb_path_query('[]', 'strict $.lower()');
    +ERROR:  jsonpath item method .lower() can only be applied to a string
    +select jsonb_path_query('{}', '$.lower()');
    +ERROR:  jsonpath item method .lower() can only be applied to a string
    +select jsonb_path_query('[]', 'strict $.lower()', silent => true);
    + jsonb_path_query 
    +------------------
    +(0 rows)
    +
    +select jsonb_path_query('{}', '$.lower()', silent => true);
    + jsonb_path_query 
    +------------------
    +(0 rows)
    +
    +select jsonb_path_query('1.23', '$.lower()');
    +ERROR:  jsonpath item method .lower() can only be applied to a string
    +select jsonb_path_query('"1.23"', '$.lower()');
    + jsonb_path_query 
    +------------------
    + "1.23"
    +(1 row)
    +
    +select jsonb_path_query('"1.23aaa"', '$.lower()');
    + jsonb_path_query 
    +------------------
    + "1.23aaa"
    +(1 row)
    +
    +select jsonb_path_query('1234', '$.lower()');
    +ERROR:  jsonpath item method .lower() can only be applied to a string
    +select jsonb_path_query('true', '$.lower()');
    +ERROR:  jsonpath item method .lower() can only be applied to a string
    +select jsonb_path_query('1234', '$.lower().type()');
    +ERROR:  jsonpath item method .lower() can only be applied to a string
    +select jsonb_path_query('[2, true]', '$.lower()');
    +ERROR:  jsonpath item method .lower() can only be applied to a string
    +select jsonb_path_query_array('["maybe", "yes", "no"]', '$[*].lower()');
    + jsonb_path_query_array 
    +------------------------
    + ["maybe", "yes", "no"]
    +(1 row)
    +
    +select jsonb_path_query_array('["maybe", "yes", "no"]', '$[*].lower().type()');
    +     jsonb_path_query_array     
    +--------------------------------
    + ["string", "string", "string"]
    +(1 row)
    +
    +-- test .upper()
    +select jsonb_path_query('null', '$.upper()');
    +ERROR:  jsonpath item method .upper() can only be applied to a string
    +select jsonb_path_query('null', '$.upper()', silent => true);
    + jsonb_path_query 
    +------------------
    +(0 rows)
    +
    +select jsonb_path_query('[]', '$.upper()');
    + jsonb_path_query 
    +------------------
    +(0 rows)
    +
    +select jsonb_path_query('[]', 'strict $.upper()');
    +ERROR:  jsonpath item method .upper() can only be applied to a string
    +select jsonb_path_query('{}', '$.upper()');
    +ERROR:  jsonpath item method .upper() can only be applied to a string
    +select jsonb_path_query('[]', 'strict $.upper()', silent => true);
    + jsonb_path_query 
    +------------------
    +(0 rows)
    +
    +select jsonb_path_query('{}', '$.upper()', silent => true);
    + jsonb_path_query 
    +------------------
    +(0 rows)
    +
    +select jsonb_path_query('1.23', '$.upper()');
    +ERROR:  jsonpath item method .upper() can only be applied to a string
    +select jsonb_path_query('"1.23"', '$.upper()');
    + jsonb_path_query 
    +------------------
    + "1.23"
    +(1 row)
    +
    +select jsonb_path_query('"1.23aaa"', '$.upper()');
    + jsonb_path_query 
    +------------------
    + "1.23AAA"
    +(1 row)
    +
    +select jsonb_path_query('1234', '$.upper()');
    +ERROR:  jsonpath item method .upper() can only be applied to a string
    +select jsonb_path_query('true', '$.upper()');
    +ERROR:  jsonpath item method .upper() can only be applied to a string
    +select jsonb_path_query('1234', '$.upper().type()');
    +ERROR:  jsonpath item method .upper() can only be applied to a string
    +select jsonb_path_query('[2, true]', '$.upper()');
    +ERROR:  jsonpath item method .upper() can only be applied to a string
    +select jsonb_path_query_array('["maybe", "yes", "no"]', '$[*].upper()');
    + jsonb_path_query_array 
    +------------------------
    + ["MAYBE", "YES", "NO"]
    +(1 row)
    +
    +select jsonb_path_query_array('["maybe", "yes", "no"]', '$[*].upper().type()');
    +     jsonb_path_query_array     
    +--------------------------------
    + ["string", "string", "string"]
    +(1 row)
    +
    +-- test .initcap()
    +select jsonb_path_query('null', '$.initcap()');
    +ERROR:  jsonpath item method .initcap() can only be applied to a string
    +select jsonb_path_query('null', '$.initcap()', silent => true);
    + jsonb_path_query 
    +------------------
    +(0 rows)
    +
    +select jsonb_path_query('[]', '$.initcap()');
    + jsonb_path_query 
    +------------------
    +(0 rows)
    +
    +select jsonb_path_query('[]', 'strict $.initcap()');
    +ERROR:  jsonpath item method .initcap() can only be applied to a string
    +select jsonb_path_query('{}', '$.initcap()');
    +ERROR:  jsonpath item method .initcap() can only be applied to a string
    +select jsonb_path_query('[]', 'strict $.initcap()', silent => true);
    + jsonb_path_query 
    +------------------
    +(0 rows)
    +
    +select jsonb_path_query('{}', '$.initcap()', silent => true);
    + jsonb_path_query 
    +------------------
    +(0 rows)
    +
    +select jsonb_path_query('1.23', '$.initcap()');
    +ERROR:  jsonpath item method .initcap() can only be applied to a string
    +select jsonb_path_query('"1.23"', '$.initcap()');
    + jsonb_path_query 
    +------------------
    + "1.23"
    +(1 row)
    +
    +select jsonb_path_query('"1.23aaa"', '$.initcap()');
    + jsonb_path_query 
    +------------------
    + "1.23aaa"
    +(1 row)
    +
    +select jsonb_path_query('1234', '$.initcap()');
    +ERROR:  jsonpath item method .initcap() can only be applied to a string
    +select jsonb_path_query('true', '$.initcap()');
    +ERROR:  jsonpath item method .initcap() can only be applied to a string
    +select jsonb_path_query('1234', '$.initcap().type()');
    +ERROR:  jsonpath item method .initcap() can only be applied to a string
    +select jsonb_path_query('[2, true]', '$.initcap()');
    +ERROR:  jsonpath item method .initcap() can only be applied to a string
    +select jsonb_path_query('["maybe yes", "probably no"]', '$.initcap()');
    + jsonb_path_query 
    +------------------
    + "Maybe Yes"
    + "Probably No"
    +(2 rows)
    +
    +-- Test .replace()
    +select jsonb_path_query('null', '$.replace("x", "bye")');
    +ERROR:  jsonpath item method .replace() can only be applied to a string
    +select jsonb_path_query('null', '$.replace("x", "bye")', silent => true);
    + jsonb_path_query 
    +------------------
    +(0 rows)
    +
    +select jsonb_path_query('["x", "y", "z"]', '$.replace("x", "bye")');
    + jsonb_path_query 
    +------------------
    + "bye"
    + "y"
    + "z"
    +(3 rows)
    +
    +select jsonb_path_query('{}', '$.replace("x", "bye")');
    +ERROR:  jsonpath item method .replace() can only be applied to a string
    +select jsonb_path_query('[]', 'strict $.replace("x", "bye")', silent => true);
    + jsonb_path_query 
    +------------------
    +(0 rows)
    +
    +select jsonb_path_query('{}', '$.replace("x", "bye")', silent => true);
    + jsonb_path_query 
    +------------------
    +(0 rows)
    +
    +select jsonb_path_query('1.23', '$.replace("x", "bye")');
    +ERROR:  jsonpath item method .replace() can only be applied to a string
    +select jsonb_path_query('"hello world"', '$.replace("hello","bye")');
    + jsonb_path_query 
    +------------------
    + "bye world"
    +(1 row)
    +
    +select jsonb_path_query('"hello world"', '$.replace("hello","bye") starts with "bye"');
    + jsonb_path_query 
    +------------------
    + true
    +(1 row)
    +
    +-- Test .split_part()
    +select jsonb_path_query('"abc~@~def~@~ghi"', '$.split_part("~@~", 2)');
    + jsonb_path_query 
    +------------------
    + "def"
    +(1 row)
    +
    +select jsonb_path_query('"abc,def,ghi,jkl"', '$.split_part(",", -2)');
    + jsonb_path_query 
    +------------------
    + "ghi"
    +(1 row)
    +
    +-- Test string methods play nicely together
    +select jsonb_path_query('"hello world"', '$.replace("hello","bye").upper()');
    + jsonb_path_query 
    +------------------
    + "BYE WORLD"
    +(1 row)
    +
    +select jsonb_path_query('"hElLo WorlD"', '$.lower().upper().lower().replace("hello","bye")');
    + jsonb_path_query 
    +------------------
    + "bye world"
    +(1 row)
    +
    +select jsonb_path_query('"hElLo WorlD"', '$.upper().lower().upper().replace("HELLO", "BYE")');
    + jsonb_path_query 
    +------------------
    + "BYE WORLD"
    +(1 row)
    +
    +select jsonb_path_query('"hElLo WorlD"', '$.lower().upper().lower().replace("hello","bye") starts with "bye"');
    + jsonb_path_query 
    +------------------
    + true
    +(1 row)
    +
    +select jsonb_path_query('"   hElLo WorlD "', '$.btrim().lower().upper().lower().replace("hello","bye") starts with "bye"');
    + jsonb_path_query 
    +------------------
    + true
    +(1 row)
    +
     -- Test .time()
     select jsonb_path_query('null', '$.time()');
     ERROR:  jsonpath item method .time() can only be applied to a string
    diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
    index fd9bd755f52..a13022feabd 100644
    --- a/src/test/regress/expected/jsonpath.out
    +++ b/src/test/regress/expected/jsonpath.out
    @@ -435,6 +435,143 @@ select '$.string()'::jsonpath;
      $.string()
     (1 row)
     
    +select '$.replace("hello","bye")'::jsonpath;
    +         jsonpath         
    +--------------------------
    + $.replace("hello","bye")
    +(1 row)
    +
    +select '$.lower()'::jsonpath;
    + jsonpath  
    +-----------
    + $.lower()
    +(1 row)
    +
    +select '$.upper()'::jsonpath;
    + jsonpath  
    +-----------
    + $.upper()
    +(1 row)
    +
    +select '$.lower().upper().lower().replace("hello","bye")'::jsonpath;
    +                     jsonpath                     
    +--------------------------------------------------
    + $.lower().upper().lower().replace("hello","bye")
    +(1 row)
    +
    +select '$.ltrim()'::jsonpath;
    + jsonpath  
    +-----------
    + $.ltrim()
    +(1 row)
    +
    +select '$.ltrim("xyz")'::jsonpath;
    +    jsonpath    
    +----------------
    + $.ltrim("xyz")
    +(1 row)
    +
    +select '$.rtrim()'::jsonpath;
    + jsonpath  
    +-----------
    + $.rtrim()
    +(1 row)
    +
    +select '$.rtrim("xyz")'::jsonpath;
    +    jsonpath    
    +----------------
    + $.rtrim("xyz")
    +(1 row)
    +
    +select '$.btrim()'::jsonpath;
    + jsonpath  
    +-----------
    + $.btrim()
    +(1 row)
    +
    +select '$.btrim("xyz")'::jsonpath;
    +    jsonpath    
    +----------------
    + $.btrim("xyz")
    +(1 row)
    +
    +select '$.initcap()'::jsonpath;
    +  jsonpath   
    +-------------
    + $.initcap()
    +(1 row)
    +
    +select '$.split_part("~@~", 2)'::jsonpath;
    +       jsonpath        
    +-----------------------
    + $.split_part("~@~",2)
    +(1 row)
    +
    +-- Parse errors
    +select '$.replace("hello")'::jsonpath;
    +ERROR:  syntax error at or near ")" of jsonpath input
    +LINE 1: select '$.replace("hello")'::jsonpath;
    +               ^
    +select '$.replace()'::jsonpath;
    +ERROR:  syntax error at or near ")" of jsonpath input
    +LINE 1: select '$.replace()'::jsonpath;
    +               ^
    +select '$.replace("hello","bye","extra")'::jsonpath;
    +ERROR:  syntax error at or near "," of jsonpath input
    +LINE 1: select '$.replace("hello","bye","extra")'::jsonpath;
    +               ^
    +select '$.split_part("~@~")'::jsonpath;
    +ERROR:  syntax error at or near ")" of jsonpath input
    +LINE 1: select '$.split_part("~@~")'::jsonpath;
    +               ^
    +select '$.split_part()'::jsonpath;
    +ERROR:  syntax error at or near ")" of jsonpath input
    +LINE 1: select '$.split_part()'::jsonpath;
    +               ^
    +select '$.split_part("~@~", "hi")'::jsonpath;
    +ERROR:  syntax error at or near """ of jsonpath input
    +LINE 1: select '$.split_part("~@~", "hi")'::jsonpath;
    +               ^
    +select '$.split_part("~@~", 2, "extra")'::jsonpath;
    +ERROR:  syntax error at or near "," of jsonpath input
    +LINE 1: select '$.split_part("~@~", 2, "extra")'::jsonpath;
    +               ^
    +select '$.lower("hi")'::jsonpath;
    +ERROR:  syntax error at or near """ of jsonpath input
    +LINE 1: select '$.lower("hi")'::jsonpath;
    +               ^
    +select '$.upper("hi")'::jsonpath;
    +ERROR:  syntax error at or near """ of jsonpath input
    +LINE 1: select '$.upper("hi")'::jsonpath;
    +               ^
    +select '$.initcap("hi")'::jsonpath;
    +ERROR:  syntax error at or near """ of jsonpath input
    +LINE 1: select '$.initcap("hi")'::jsonpath;
    +               ^
    +select '$.ltrim(42)'::jsonpath;
    +ERROR:  syntax error at or near "42" of jsonpath input
    +LINE 1: select '$.ltrim(42)'::jsonpath;
    +               ^
    +select '$.ltrim("x", "y")'::jsonpath;
    +ERROR:  syntax error at or near "," of jsonpath input
    +LINE 1: select '$.ltrim("x", "y")'::jsonpath;
    +               ^
    +select '$.rtrim(42)'::jsonpath;
    +ERROR:  syntax error at or near "42" of jsonpath input
    +LINE 1: select '$.rtrim(42)'::jsonpath;
    +               ^
    +select '$.rtrim("x", "y")'::jsonpath;
    +ERROR:  syntax error at or near "," of jsonpath input
    +LINE 1: select '$.rtrim("x", "y")'::jsonpath;
    +               ^
    +select '$.trim(42)'::jsonpath;
    +ERROR:  syntax error at or near "(" of jsonpath input
    +LINE 1: select '$.trim(42)'::jsonpath;
    +               ^
    +select '$.trim("x", "y")'::jsonpath;
    +ERROR:  syntax error at or near "(" of jsonpath input
    +LINE 1: select '$.trim("x", "y")'::jsonpath;
    +               ^
     select '$.time()'::jsonpath;
      jsonpath 
     ----------
    diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
    index 3e8929a5269..8169719343e 100644
    --- a/src/test/regress/sql/jsonb_jsonpath.sql
    +++ b/src/test/regress/sql/jsonb_jsonpath.sql
    @@ -623,6 +623,115 @@ select jsonb_path_query('"2023-08-15 12:34:56 +5:30"', '$.timestamp_tz().string(
     select jsonb_path_query('"2023-08-15 12:34:56"', '$.timestamp().string()');
     rollback;
     
    +-- test .ltrim()
    +select jsonb_path_query('"   hello   "', '$.ltrim(" ")');
    +select jsonb_path_query('"   hello   "', '$.ltrim(" ")');
    +select jsonb_path_query('"   hello   "', '$.ltrim()');
    +select jsonb_path_query('"zzzytest"', '$.ltrim("xyz")');
    +select jsonb_path_query('null', '$.ltrim()');
    +select jsonb_path_query('null', '$.ltrim()', silent => true);
    +select jsonb_path_query('[]', '$.ltrim()');
    +select jsonb_path_query('[]', 'strict $.ltrim()');
    +select jsonb_path_query('{}', '$.ltrim()');
    +select jsonb_path_query('[]', 'strict $.ltrim()', silent => true);
    +select jsonb_path_query('{}', '$.ltrim()', silent => true);
    +select jsonb_path_query('1.23', '$.ltrim()');
    +select jsonb_path_query('"1.23"', '$.ltrim()');
    +select jsonb_path_query('"1.23aaa"', '$.ltrim()');
    +select jsonb_path_query('1234', '$.ltrim()');
    +select jsonb_path_query('true', '$.ltrim()');
    +select jsonb_path_query('1234', '$.ltrim().type()');
    +select jsonb_path_query('[2, true]', '$.ltrim()');
    +select jsonb_path_query_array('["  maybe  ", "  yes", "  no"]', '$[*].ltrim()');
    +select jsonb_path_query_array('["  maybe  ", "  yes", "  no"]', '$[*].ltrim().type()');
    +
    +-- test .rtrim()
    +select jsonb_path_query('"   hello   "', '$.rtrim(" ")');
    +select jsonb_path_query('"testxxzx"', '$.rtrim("xyz")');
    +select jsonb_path_query('"   hello   "', '$.rtrim()');
    +select jsonb_path_query('"   hello   "', '$.rtrim()');
    +
    +-- test .btrim()
    +select jsonb_path_query('"   hello   "', '$.btrim(" ")');
    +select jsonb_path_query('"xyxtrimyyx"', '$.btrim("xyz")');
    +select jsonb_path_query('"   hello   "', '$.btrim()');
    +select jsonb_path_query('"   hello   "', '$.btrim()');
    +
    +-- test .lower()
    +select jsonb_path_query('null', '$.lower()');
    +select jsonb_path_query('null', '$.lower()', silent => true);
    +select jsonb_path_query('[]', '$.lower()');
    +select jsonb_path_query('[]', 'strict $.lower()');
    +select jsonb_path_query('{}', '$.lower()');
    +select jsonb_path_query('[]', 'strict $.lower()', silent => true);
    +select jsonb_path_query('{}', '$.lower()', silent => true);
    +select jsonb_path_query('1.23', '$.lower()');
    +select jsonb_path_query('"1.23"', '$.lower()');
    +select jsonb_path_query('"1.23aaa"', '$.lower()');
    +select jsonb_path_query('1234', '$.lower()');
    +select jsonb_path_query('true', '$.lower()');
    +select jsonb_path_query('1234', '$.lower().type()');
    +select jsonb_path_query('[2, true]', '$.lower()');
    +select jsonb_path_query_array('["maybe", "yes", "no"]', '$[*].lower()');
    +select jsonb_path_query_array('["maybe", "yes", "no"]', '$[*].lower().type()');
    +
    +-- test .upper()
    +select jsonb_path_query('null', '$.upper()');
    +select jsonb_path_query('null', '$.upper()', silent => true);
    +select jsonb_path_query('[]', '$.upper()');
    +select jsonb_path_query('[]', 'strict $.upper()');
    +select jsonb_path_query('{}', '$.upper()');
    +select jsonb_path_query('[]', 'strict $.upper()', silent => true);
    +select jsonb_path_query('{}', '$.upper()', silent => true);
    +select jsonb_path_query('1.23', '$.upper()');
    +select jsonb_path_query('"1.23"', '$.upper()');
    +select jsonb_path_query('"1.23aaa"', '$.upper()');
    +select jsonb_path_query('1234', '$.upper()');
    +select jsonb_path_query('true', '$.upper()');
    +select jsonb_path_query('1234', '$.upper().type()');
    +select jsonb_path_query('[2, true]', '$.upper()');
    +select jsonb_path_query_array('["maybe", "yes", "no"]', '$[*].upper()');
    +select jsonb_path_query_array('["maybe", "yes", "no"]', '$[*].upper().type()');
    +
    +-- test .initcap()
    +select jsonb_path_query('null', '$.initcap()');
    +select jsonb_path_query('null', '$.initcap()', silent => true);
    +select jsonb_path_query('[]', '$.initcap()');
    +select jsonb_path_query('[]', 'strict $.initcap()');
    +select jsonb_path_query('{}', '$.initcap()');
    +select jsonb_path_query('[]', 'strict $.initcap()', silent => true);
    +select jsonb_path_query('{}', '$.initcap()', silent => true);
    +select jsonb_path_query('1.23', '$.initcap()');
    +select jsonb_path_query('"1.23"', '$.initcap()');
    +select jsonb_path_query('"1.23aaa"', '$.initcap()');
    +select jsonb_path_query('1234', '$.initcap()');
    +select jsonb_path_query('true', '$.initcap()');
    +select jsonb_path_query('1234', '$.initcap().type()');
    +select jsonb_path_query('[2, true]', '$.initcap()');
    +select jsonb_path_query('["maybe yes", "probably no"]', '$.initcap()');
    +
    +-- Test .replace()
    +select jsonb_path_query('null', '$.replace("x", "bye")');
    +select jsonb_path_query('null', '$.replace("x", "bye")', silent => true);
    +select jsonb_path_query('["x", "y", "z"]', '$.replace("x", "bye")');
    +select jsonb_path_query('{}', '$.replace("x", "bye")');
    +select jsonb_path_query('[]', 'strict $.replace("x", "bye")', silent => true);
    +select jsonb_path_query('{}', '$.replace("x", "bye")', silent => true);
    +select jsonb_path_query('1.23', '$.replace("x", "bye")');
    +select jsonb_path_query('"hello world"', '$.replace("hello","bye")');
    +select jsonb_path_query('"hello world"', '$.replace("hello","bye") starts with "bye"');
    +
    +-- Test .split_part()
    +select jsonb_path_query('"abc~@~def~@~ghi"', '$.split_part("~@~", 2)');
    +select jsonb_path_query('"abc,def,ghi,jkl"', '$.split_part(",", -2)');
    +
    +-- Test string methods play nicely together
    +select jsonb_path_query('"hello world"', '$.replace("hello","bye").upper()');
    +select jsonb_path_query('"hElLo WorlD"', '$.lower().upper().lower().replace("hello","bye")');
    +select jsonb_path_query('"hElLo WorlD"', '$.upper().lower().upper().replace("HELLO", "BYE")');
    +select jsonb_path_query('"hElLo WorlD"', '$.lower().upper().lower().replace("hello","bye") starts with "bye"');
    +select jsonb_path_query('"   hElLo WorlD "', '$.btrim().lower().upper().lower().replace("hello","bye") starts with "bye"');
    +
     -- Test .time()
     select jsonb_path_query('null', '$.time()');
     select jsonb_path_query('true', '$.time()');
    diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
    index 61a5270d4e8..7dd2e57e7a6 100644
    --- a/src/test/regress/sql/jsonpath.sql
    +++ b/src/test/regress/sql/jsonpath.sql
    @@ -78,6 +78,37 @@ select '$.boolean()'::jsonpath;
     select '$.date()'::jsonpath;
     select '$.decimal(4,2)'::jsonpath;
     select '$.string()'::jsonpath;
    +select '$.replace("hello","bye")'::jsonpath;
    +select '$.lower()'::jsonpath;
    +select '$.upper()'::jsonpath;
    +select '$.lower().upper().lower().replace("hello","bye")'::jsonpath;
    +select '$.ltrim()'::jsonpath;
    +select '$.ltrim("xyz")'::jsonpath;
    +select '$.rtrim()'::jsonpath;
    +select '$.rtrim("xyz")'::jsonpath;
    +select '$.btrim()'::jsonpath;
    +select '$.btrim("xyz")'::jsonpath;
    +select '$.initcap()'::jsonpath;
    +select '$.split_part("~@~", 2)'::jsonpath;
    +
    +-- Parse errors
    +select '$.replace("hello")'::jsonpath;
    +select '$.replace()'::jsonpath;
    +select '$.replace("hello","bye","extra")'::jsonpath;
    +select '$.split_part("~@~")'::jsonpath;
    +select '$.split_part()'::jsonpath;
    +select '$.split_part("~@~", "hi")'::jsonpath;
    +select '$.split_part("~@~", 2, "extra")'::jsonpath;
    +select '$.lower("hi")'::jsonpath;
    +select '$.upper("hi")'::jsonpath;
    +select '$.initcap("hi")'::jsonpath;
    +select '$.ltrim(42)'::jsonpath;
    +select '$.ltrim("x", "y")'::jsonpath;
    +select '$.rtrim(42)'::jsonpath;
    +select '$.rtrim("x", "y")'::jsonpath;
    +select '$.trim(42)'::jsonpath;
    +select '$.trim("x", "y")'::jsonpath;
    +
     select '$.time()'::jsonpath;
     select '$.time(6)'::jsonpath;
     select '$.time_tz()'::jsonpath;
    -- 
    2.47.3
    
    
    --i3zvfoojvjxpz4h6--