v1-0003-Add-jsonpath-.join-sep-method.patch
application/octet-stream
Filename: v1-0003-Add-jsonpath-.join-sep-method.patch
Type: application/octet-stream
Part: 2
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 v1-0003
Subject: Add jsonpath $.join(sep) method
| File | + | − |
|---|---|---|
| doc/src/sgml/func/func-json.sgml | 18 | 0 |
| src/backend/utils/adt/jsonpath.c | 22 | 0 |
| src/backend/utils/adt/jsonpath_exec.c | 111 | 0 |
| src/backend/utils/adt/jsonpath_gram.y | 6 | 1 |
| src/backend/utils/adt/jsonpath_scan.l | 1 | 0 |
| src/include/utils/jsonpath.h | 1 | 0 |
| src/test/regress/expected/jsonb_jsonpath.out | 80 | 0 |
| src/test/regress/expected/jsonpath.out | 32 | 0 |
| src/test/regress/expected/sqljson_queryfuncs.out | 1 | 0 |
| src/test/regress/sql/jsonb_jsonpath.sql | 28 | 0 |
| src/test/regress/sql/jsonpath.sql | 7 | 0 |
| src/test/regress/sql/sqljson_queryfuncs.sql | 1 | 0 |
From 892f5574b251773a3cb8f14b5e18902bf6871200 Mon Sep 17 00:00:00 2001
From: Florents Tselai <florents.tselai@gmail.com>
Date: Mon, 13 Apr 2026 12:07:46 +0300
Subject: [PATCH v1 3/3] Add jsonpath $.join(sep) method
---
doc/src/sgml/func/func-json.sgml | 18 +++
src/backend/utils/adt/jsonpath.c | 22 ++++
src/backend/utils/adt/jsonpath_exec.c | 111 ++++++++++++++++++
src/backend/utils/adt/jsonpath_gram.y | 7 +-
src/backend/utils/adt/jsonpath_scan.l | 1 +
src/include/utils/jsonpath.h | 1 +
src/test/regress/expected/jsonb_jsonpath.out | 80 +++++++++++++
src/test/regress/expected/jsonpath.out | 32 +++++
.../regress/expected/sqljson_queryfuncs.out | 1 +
src/test/regress/sql/jsonb_jsonpath.sql | 28 +++++
src/test/regress/sql/jsonpath.sql | 7 ++
src/test/regress/sql/sqljson_queryfuncs.sql | 1 +
12 files changed, 308 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/func/func-json.sgml b/doc/src/sgml/func/func-json.sgml
index 7aad2d69a88..715bdf62578 100644
--- a/doc/src/sgml/func/func-json.sgml
+++ b/doc/src/sgml/func/func-json.sgml
@@ -2959,6 +2959,24 @@ ERROR: jsonpath member accessor can only be applied to an object
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <replaceable>array</replaceable> <literal>.</literal> <literal>join(separator [, null_string])</literal>
+ <returnvalue>string</returnvalue>
+ </para>
+ <para>
+ Concatenates the elements of an array into a single string using the
+ specified separator. If the optional <literal>null_string</literal>
+ argument is provided, it replaces JSON <literal>null</literal> values;
+ otherwise, <literal>null</literal> values are skipped. The input must
+ be an array consisting only of strings or <literal>null</literal> values.
+ </para>
+ <para>
+ <literal>jsonb_path_query('["a", null, "c"]', '$.join("-", "N/A")')</literal>
+ <returnvalue>"a-N/A-c"</returnvalue>
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index d14fe450b6b..b501f197964 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -330,6 +330,7 @@ flattenJsonPathParseItem(StringInfo buf, int *result, struct Node *escontext,
}
break;
case jpiStrSplit:
+ case jpiStrJoin:
{
/* Reserve space for left and right arg positions */
int32 left = reserveSpaceForItemPointer(buf);
@@ -946,6 +947,20 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
printJsonPathItem(buf, &elem, false, false);
appendStringInfoChar(buf, ')');
break;
+ case jpiStrJoin:
+ appendStringInfoString(buf, ".join(");
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+
+ /* Check if null_string was provided. */
+ if (v->content.args.right != 0)
+ {
+ appendStringInfoString(buf, ", ");
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ appendStringInfoChar(buf, ')');
+ break;
default:
elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
}
@@ -1049,6 +1064,8 @@ jspOperationName(JsonPathItemType type)
return "translate";
case jpiStrSplit:
return "split";
+ case jpiStrJoin:
+ return "join";
default:
elog(ERROR, "unrecognized jsonpath item type: %d", type);
return NULL;
@@ -1183,6 +1200,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
case jpiStrTranslate:
case jpiStrSplit:
case jpiStrSplitPart:
+ case jpiStrJoin:
read_int32(v->content.args.left, base, pos);
read_int32(v->content.args.right, base, pos);
break;
@@ -1310,6 +1328,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
v->type == jpiStrInitcap ||
v->type == jpiStrSplitPart ||
v->type == jpiStrSplit ||
+ v->type == jpiStrJoin ||
v->type == jpiStrTranslate);
if (a)
@@ -1341,6 +1360,7 @@ jspGetLeftArg(JsonPathItem *v, JsonPathItem *a)
v->type == jpiStrReplace ||
v->type == jpiStrTranslate ||
v->type == jpiStrSplit ||
+ v->type == jpiStrJoin ||
v->type == jpiStrSplitPart);
jspInitByBuffer(a, v->base, v->content.args.left);
@@ -1367,6 +1387,7 @@ jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
v->type == jpiStrReplace ||
v->type == jpiStrTranslate ||
v->type == jpiStrSplitPart ||
+ v->type == jpiStrJoin ||
v->type == jpiStrSplit);
jspInitByBuffer(a, v->base, v->content.args.right);
@@ -1677,6 +1698,7 @@ jspIsMutableWalker(JsonPathItem *jpi, struct JsonPathMutableContext *cxt)
case jpiStrSplitPart:
case jpiStrSplit:
case jpiStrTranslate:
+ case jpiStrJoin:
status = jpdsNonDateTime;
break;
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 93f31b40b0b..cb8bb1aac25 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -1699,6 +1699,117 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
return executeStringInternalMethod(cxt, jsp, jb, found);
}
break;
+ case jpiStrJoin:
+ {
+ JsonPathItem next_elem;
+ JsonbValue jbv_res;
+ char *sep;
+ char *null_replace = NULL;
+ StringInfoData buf;
+ bool first = true;
+ bool hasNext;
+
+ jspGetLeftArg(jsp, &elem);
+ sep = jspGetString(&elem, NULL);
+
+ if (jsp->content.args.right != 0)
+ {
+ jspGetRightArg(jsp, &elem);
+ null_replace = jspGetString(&elem, NULL);
+ }
+
+ /* Validate target is an array */
+ if (JsonbType(jb) != jbvArray)
+ {
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("jsonpath item method .join() can only be applied to an array"))));
+ }
+
+ initStringInfo(&buf);
+
+ /* Process the array elements */
+ if (jb->type == jbvBinary)
+ {
+ /* Serialized Binary Blob */
+ JsonbIterator *it;
+ JsonbValue v;
+ JsonbIteratorToken tok;
+
+ it = JsonbIteratorInit(jb->val.binary.data);
+ while ((tok = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (tok != WJB_ELEM)
+ continue;
+
+ if (v.type == jbvString)
+ {
+ if (!first)
+ appendStringInfoString(&buf, sep);
+ appendBinaryStringInfo(&buf, v.val.string.val, v.val.string.len);
+ first = false;
+ }
+ else if (v.type == jbvNull)
+ {
+ if (null_replace)
+ {
+ if (!first)
+ appendStringInfoString(&buf, sep);
+ appendStringInfoString(&buf, null_replace);
+ first = false;
+ }
+ }
+ else
+ {
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("jsonpath .join() array elements must be strings or nulls"))));
+ }
+ }
+ }
+ else
+ {
+ /* Recursive Tree (jbvArray) */
+ for (int i = 0; i < jb->val.array.nElems; i++)
+ {
+ JsonbValue *v = &jb->val.array.elems[i];
+
+ if (v->type == jbvString)
+ {
+ if (!first)
+ appendStringInfoString(&buf, sep);
+ appendBinaryStringInfo(&buf, v->val.string.val, v->val.string.len);
+ first = false;
+ }
+ else if (v->type == jbvNull)
+ {
+ if (null_replace)
+ {
+ if (!first)
+ appendStringInfoString(&buf, sep);
+ appendStringInfoString(&buf, null_replace);
+ first = false;
+ }
+ }
+ else
+ {
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("jsonpath .join() array elements must be strings or nulls"))));
+ }
+ }
+ }
+
+ jbv_res.type = jbvString;
+ jbv_res.val.string.val = buf.data;
+ jbv_res.val.string.len = buf.len;
+
+ hasNext = jspGetNext(jsp, &next_elem);
+ if (!hasNext && !found)
+ return jperOk;
+
+ return executeNextItem(cxt, jsp, hasNext ? &next_elem : NULL, &jbv_res, found);
+ }
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 fe30e8a6147..eb2b952e311 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -87,7 +87,7 @@ static bool makeItemLikeRegex(JsonPathParseItem *expr,
%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_P STR_SPLIT_PART_P STR_TRANSLATE_P
+ STR_INITCAP_P STR_SPLIT_P STR_SPLIT_PART_P STR_TRANSLATE_P STR_JOIN_P
%type <result> result
@@ -290,6 +290,10 @@ accessor_op:
{ $$ = makeItemBinary(jpiStrSplit, $4, NULL); }
| '.' STR_SPLIT_P '(' str_str_args ')'
{ $$ = makeItemBinary(jpiStrSplit, linitial($4), lsecond($4)); }
+ | '.' STR_JOIN_P '(' str_elem ')'
+ { $$ = makeItemBinary(jpiStrJoin, $4, NULL); }
+ | '.' STR_JOIN_P '(' str_str_args ')'
+ { $$ = makeItemBinary(jpiStrJoin, linitial($4), lsecond($4)); }
| '.' STR_LTRIM_P '(' opt_str_arg ')'
{ $$ = makeItemUnary(jpiStrLtrim, $4); }
| '.' STR_RTRIM_P '(' opt_str_arg ')'
@@ -393,6 +397,7 @@ key_name:
| STR_LTRIM_P
| STR_RTRIM_P
| STR_BTRIM_P
+ | STR_JOIN_P
;
method:
diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
index b06d0abb64a..bd49941a8fc 100644
--- a/src/backend/utils/adt/jsonpath_scan.l
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -406,6 +406,7 @@ static const JsonPathKeyword keywords[] = {
{3, false, LAX_P, "lax"},
{4, false, DATE_P, "date"},
{4, false, FLAG_P, "flag"},
+ {4, false, STR_JOIN_P, "join"},
{4, false, LAST_P, "last"},
{4, true, NULL_P, "null"},
{4, false, SIZE_P, "size"},
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index c1c7812a36a..f61639e3767 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -115,6 +115,7 @@ typedef enum JsonPathItemType
jpiTimeTz, /* .time_tz() item method */
jpiTimestamp, /* .timestamp() item method */
jpiTimestampTz, /* .timestamp_tz() item method */
+ jpiStrJoin, /* .join() item method */
jpiStrReplace, /* .replace() item method */
jpiStrLower, /* .lower() item method */
jpiStrUpper, /* .upper() item method */
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index 8fc530201a6..7e247ba7505 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -3135,6 +3135,80 @@ select jsonb_path_query('"a,b,c"', '$.split(",")[1]');
"b"
(1 row)
+-- Test .join() method
+select jsonb_path_query('["a", "b", "c"]', '$.join("-")');
+ jsonb_path_query
+------------------
+ "a-b-c"
+(1 row)
+
+-- Join with null replacement
+select jsonb_path_query('["a", "b", "c"]', '$.join("-", "N/A")');
+ jsonb_path_query
+------------------
+ "a-b-c"
+(1 row)
+
+-- Null handling: default (skip)
+select jsonb_path_query('["a", null, "c"]', '$.join("-")');
+ jsonb_path_query
+------------------
+ "a-c"
+(1 row)
+
+-- Null handling: replacement
+select jsonb_path_query('["a", null, "c"]', '$.join("-", "N/A")');
+ jsonb_path_query
+------------------
+ "a-N/A-c"
+(1 row)
+
+-- Empty array (should return empty string)
+select jsonb_path_query('[]', '$.join("-")');
+ jsonb_path_query
+------------------
+ ""
+(1 row)
+
+-- Pipeline integration: .join().upper()
+select jsonb_path_query('["hello", "world"]', '$.join(" ").upper()');
+ jsonb_path_query
+------------------
+ "HELLO WORLD"
+(1 row)
+
+-- Pipeline integration: .split().join()
+select jsonb_path_query('"a,b,c"', '$.split(",").join("|")');
+ jsonb_path_query
+------------------
+ "a|b|c"
+(1 row)
+
+-- Error case: Non-string element (should trigger our ereport)
+select jsonb_path_query('[1, "a"]', '$.join("-")');
+ERROR: jsonpath .join() array elements must be strings or nulls
+-- Error case: Nested object
+select jsonb_path_query('["a", {"b": 1}]', '$.join("-")');
+ERROR: jsonpath .join() array elements must be strings or nulls
+-- Error case: Applied to a scalar
+select jsonb_path_query('"not an array"', '$.join("-")');
+ERROR: jsonpath item method .join() can only be applied to an array
+-- Lax mode: should still error under current conservative implementation
+select jsonb_path_query('[1, "a"]', 'lax $.join("-")');
+ERROR: jsonpath .join() array elements must be strings or nulls
+select jsonb_path_query('"not an array"', 'lax $.join("-")');
+ERROR: jsonpath item method .join() can only be applied to an array
+-- Silent mode: should suppress errors and return no rows
+select jsonb_path_query('[1, "a"]', '$.join("-")', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('"not an array"', '$.join("-")', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
-- Test string methods play nicely together
select jsonb_path_query('"hello world"', '$.replace("hello","bye").upper()');
jsonb_path_query
@@ -3166,6 +3240,12 @@ select jsonb_path_query('" hElLo WorlD "', '$.btrim().lower().upper().lower().
true
(1 row)
+select jsonb_path_query('" A,b,C "', '$.btrim().lower().split(",").join("-").replace("a","x").upper() starts with "X-B"');
+ 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 a9ef9cefced..6b64603a0bf 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -519,6 +519,18 @@ select '$.split(",")'::jsonpath;
$.split(",")
(1 row)
+select '$.join(",")'::jsonpath;
+ jsonpath
+-------------
+ $.join(",")
+(1 row)
+
+select '$.join(",", "N/A")'::jsonpath;
+ jsonpath
+--------------------
+ $.join(",", "N/A")
+(1 row)
+
-- Parse errors
select '$.replace("hello")'::jsonpath;
ERROR: syntax error at or near ")" of jsonpath input
@@ -655,6 +667,26 @@ select '$.btrim'::jsonpath;
$."btrim"
(1 row)
+select '$.join()'::jsonpath;
+ERROR: syntax error at or near ")" of jsonpath input
+LINE 1: select '$.join()'::jsonpath;
+ ^
+select '$.join(",", "replacement", "extra")'::jsonpath;
+ERROR: syntax error at or near "," of jsonpath input
+LINE 1: select '$.join(",", "replacement", "extra")'::jsonpath;
+ ^
+select '$.join(42)'::jsonpath;
+ERROR: syntax error at or near "42" of jsonpath input
+LINE 1: select '$.join(42)'::jsonpath;
+ ^
+select '$.join(true)'::jsonpath;
+ERROR: syntax error at end of jsonpath input
+LINE 1: select '$.join(true)'::jsonpath;
+ ^
+select '$.join("x", 123)'::jsonpath;
+ERROR: syntax error at or near "123" of jsonpath input
+LINE 1: select '$.join("x", 123)'::jsonpath;
+ ^
select '$.time()'::jsonpath;
jsonpath
----------
diff --git a/src/test/regress/expected/sqljson_queryfuncs.out b/src/test/regress/expected/sqljson_queryfuncs.out
index c3fc890a9c4..2df3e34d0e0 100644
--- a/src/test/regress/expected/sqljson_queryfuncs.out
+++ b/src/test/regress/expected/sqljson_queryfuncs.out
@@ -1282,6 +1282,7 @@ CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.replace("hello", "bye")
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.translate("hello", "bye")'));
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.split_part(",", 2)'));
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.split(",")'));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.join(",")'));
-- DEFAULT expression
CREATE OR REPLACE FUNCTION ret_setint() RETURNS SETOF integer AS
$$
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index 784fbc577f2..86f7d2fe4b6 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -739,12 +739,40 @@ select jsonb_path_query('"a,,c"', '$.split(",", "")');
-- proving the output is a real, indexable JSON array
select jsonb_path_query('"a,b,c"', '$.split(",")[1]');
+-- Test .join() method
+select jsonb_path_query('["a", "b", "c"]', '$.join("-")');
+-- Join with null replacement
+select jsonb_path_query('["a", "b", "c"]', '$.join("-", "N/A")');
+-- Null handling: default (skip)
+select jsonb_path_query('["a", null, "c"]', '$.join("-")');
+-- Null handling: replacement
+select jsonb_path_query('["a", null, "c"]', '$.join("-", "N/A")');
+-- Empty array (should return empty string)
+select jsonb_path_query('[]', '$.join("-")');
+-- Pipeline integration: .join().upper()
+select jsonb_path_query('["hello", "world"]', '$.join(" ").upper()');
+-- Pipeline integration: .split().join()
+select jsonb_path_query('"a,b,c"', '$.split(",").join("|")');
+-- Error case: Non-string element (should trigger our ereport)
+select jsonb_path_query('[1, "a"]', '$.join("-")');
+-- Error case: Nested object
+select jsonb_path_query('["a", {"b": 1}]', '$.join("-")');
+-- Error case: Applied to a scalar
+select jsonb_path_query('"not an array"', '$.join("-")');
+-- Lax mode: should still error under current conservative implementation
+select jsonb_path_query('[1, "a"]', 'lax $.join("-")');
+select jsonb_path_query('"not an array"', 'lax $.join("-")');
+-- Silent mode: should suppress errors and return no rows
+select jsonb_path_query('[1, "a"]', '$.join("-")', silent => true);
+select jsonb_path_query('"not an array"', '$.join("-")', silent => true);
+
-- 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"');
+select jsonb_path_query('" A,b,C "', '$.btrim().lower().split(",").join("-").replace("a","x").upper() starts with "X-B"');
-- Test .time()
select jsonb_path_query('null', '$.time()');
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index ef27c8e7bb6..c524dc7a3d4 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -92,6 +92,8 @@ select '$.btrim("xyz")'::jsonpath;
select '$.initcap()'::jsonpath;
select '$.split_part("~@~", 2)'::jsonpath;
select '$.split(",")'::jsonpath;
+select '$.join(",")'::jsonpath;
+select '$.join(",", "N/A")'::jsonpath;
-- Parse errors
select '$.replace("hello")'::jsonpath;
@@ -125,6 +127,11 @@ select '$.split'::jsonpath;
select '$.ltrim'::jsonpath;
select '$.rtrim'::jsonpath;
select '$.btrim'::jsonpath;
+select '$.join()'::jsonpath;
+select '$.join(",", "replacement", "extra")'::jsonpath;
+select '$.join(42)'::jsonpath;
+select '$.join(true)'::jsonpath;
+select '$.join("x", 123)'::jsonpath;
select '$.time()'::jsonpath;
select '$.time(6)'::jsonpath;
diff --git a/src/test/regress/sql/sqljson_queryfuncs.sql b/src/test/regress/sql/sqljson_queryfuncs.sql
index 83ddfc8bc83..ba9d7b7ee47 100644
--- a/src/test/regress/sql/sqljson_queryfuncs.sql
+++ b/src/test/regress/sql/sqljson_queryfuncs.sql
@@ -412,6 +412,7 @@ CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.replace("hello", "bye")
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.translate("hello", "bye")'));
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.split_part(",", 2)'));
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.split(",")'));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.join(",")'));
-- DEFAULT expression
CREATE OR REPLACE FUNCTION ret_setint() RETURNS SETOF integer AS
--
2.53.0