From 64f96df170654560cd75c9c71a346078f5530d8d Mon Sep 17 00:00:00 2001 From: Mustafa Senoglu Date: Sun, 13 Sep 2026 20:02:32 +0300 Subject: [PATCH 1/3] fix: treat split() delimiter as literal string, not regex (#2568) Signed-off-by: Mustafa Senoglu --- regress/expected/expr.out | 50 ++++++++++++++++++++++++++++++++++ regress/sql/expr.sql | 16 +++++++++++ src/backend/utils/adt/agtype.c | 7 ++++- 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/regress/expected/expr.out b/regress/expected/expr.out index 806a6f65c..e23b0945a 100644 --- a/regress/expected/expr.out +++ b/regress/expected/expr.out @@ -5014,6 +5014,56 @@ ERROR: function age_split() does not exist LINE 1: SELECT * FROM age_split(); ^ HINT: No function matches the given name and argument types. You might need to add explicit type casts. +-- regex metacharacters should be treated as literal delimiters (#2568) +SELECT * FROM cypher('expr', $$ + RETURN split("a.b.c", ".") +$$) AS (results agtype); + results +--------------- + ["a", "b", "c"] +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN split("a|b|c", "|") +$$) AS (results agtype); + results +--------------- + ["a", "b", "c"] +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN split("a+b+c", "+") +$$) AS (results agtype); + results +--------------- + ["a", "b", "c"] +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN split("a.b.c.d", ".") +$$) AS (results agtype); + results +-------------------- + ["a", "b", "c", "d"] +(1 row) + +SELECT * FROM age_split('a.b.c', '.'); + results +-------------------- + ["a", "b", "c"] +(1 row) + +SELECT * FROM age_split('a|b|c', '|'); + results +-------------------- + ["a", "b", "c"] +(1 row) + +SELECT * FROM age_split('a+b+c', '+'); + results +-------------------- + ["a", "b", "c"] +(1 row) -- -- replace() -- diff --git a/regress/sql/expr.sql b/regress/sql/expr.sql index d4d900a1c..3f382119f 100644 --- a/regress/sql/expr.sql +++ b/regress/sql/expr.sql @@ -2098,6 +2098,22 @@ SELECT * FROM age_split(123456789, ','); SELECT * FROM age_split('a,b,c,d,e,f', -1); SELECT * FROM age_split('a,b,c,d,e,f'); SELECT * FROM age_split(); +-- regex metacharacters should be treated as literal delimiters (#2568) +SELECT * FROM cypher('expr', $$ + RETURN split("a.b.c", ".") +$$) AS (results agtype); +SELECT * FROM cypher('expr', $$ + RETURN split("a|b|c", "|") +$$) AS (results agtype); +SELECT * FROM cypher('expr', $$ + RETURN split("a+b+c", "+") +$$) AS (results agtype); +SELECT * FROM cypher('expr', $$ + RETURN split("a.b.c.d", ".") +$$) AS (results agtype); +SELECT * FROM age_split('a.b.c', '.'); +SELECT * FROM age_split('a|b|c', '|'); +SELECT * FROM age_split('a+b+c', '+'); -- -- replace() diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index cc9cc7717..1df14eb6b 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -9480,8 +9480,13 @@ Datum age_split(PG_FUNCTION_ARGS) /* * We need the strings as a text strings so that we can let PG deal with * multibyte characters in the string. The result is an ArrayType + * + * Use string_to_array (literal split) instead of regexp_split_to_array + * so that the delimiter is treated as a plain string, not a regular + * expression. This matches openCypher semantics and is consistent with + * age_replace(), which already treats the delimiter literally. */ - text_array = DirectFunctionCall2Coll(regexp_split_to_array, + text_array = DirectFunctionCall2Coll(string_to_array, DEFAULT_COLLATION_OID, PointerGetDatum(text_string), PointerGetDatum(text_delimiter)); From 624e02383525ee084c2c6b5ae44099973b8298b6 Mon Sep 17 00:00:00 2001 From: Mustafa Senoglu Date: Mon, 12 Oct 2026 15:13:09 +0300 Subject: [PATCH 2/3] fix: use text_to_array C function in age_split The SQL function string_to_array maps to C text_to_array, there is no string_to_array symbol which broke the PG18 build. Signed-off-by: Mustafa Senoglu --- src/backend/utils/adt/agtype.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index 1df14eb6b..8de018a6b 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -9481,12 +9481,12 @@ Datum age_split(PG_FUNCTION_ARGS) * We need the strings as a text strings so that we can let PG deal with * multibyte characters in the string. The result is an ArrayType * - * Use string_to_array (literal split) instead of regexp_split_to_array + * Use text_to_array (SQL string_to_array, literal split) instead of regexp_split_to_array * so that the delimiter is treated as a plain string, not a regular * expression. This matches openCypher semantics and is consistent with * age_replace(), which already treats the delimiter literally. */ - text_array = DirectFunctionCall2Coll(string_to_array, + text_array = DirectFunctionCall2Coll(text_to_array, DEFAULT_COLLATION_OID, PointerGetDatum(text_string), PointerGetDatum(text_delimiter)); From a5505e445cbb490d0846253d8e8586e3b85671ad Mon Sep 17 00:00:00 2001 From: Mustafa Senoglu Date: Thu, 15 Oct 2026 13:39:58 +0300 Subject: [PATCH 3/3] Fix expr.out: match literal-split output (col widths, age_split name, empty-delim) --- regress/expected/expr.out | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/regress/expected/expr.out b/regress/expected/expr.out index e23b0945a..6299ce9e5 100644 --- a/regress/expected/expr.out +++ b/regress/expected/expr.out @@ -4903,9 +4903,9 @@ $$) AS (results agtype); SELECT * FROM cypher('expr', $$ RETURN split("a,b,c,d,e,f", "") $$) AS (results agtype); - results ---------------------------------------------------------- - ["a", ",", "b", ",", "c", ",", "d", ",", "e", ",", "f"] + results +----------------- + ["a,b,c,d,e,f"] (1 row) SELECT * FROM cypher('expr', $$ @@ -5018,52 +5018,53 @@ HINT: No function matches the given name and argument types. You might need to SELECT * FROM cypher('expr', $$ RETURN split("a.b.c", ".") $$) AS (results agtype); - results ---------------- + results +----------------- ["a", "b", "c"] (1 row) SELECT * FROM cypher('expr', $$ RETURN split("a|b|c", "|") $$) AS (results agtype); - results ---------------- + results +----------------- ["a", "b", "c"] (1 row) SELECT * FROM cypher('expr', $$ RETURN split("a+b+c", "+") $$) AS (results agtype); - results ---------------- + results +----------------- ["a", "b", "c"] (1 row) SELECT * FROM cypher('expr', $$ RETURN split("a.b.c.d", ".") $$) AS (results agtype); - results --------------------- + results +---------------------- ["a", "b", "c", "d"] (1 row) SELECT * FROM age_split('a.b.c', '.'); - results --------------------- + age_split +----------------- ["a", "b", "c"] (1 row) SELECT * FROM age_split('a|b|c', '|'); - results --------------------- + age_split +----------------- ["a", "b", "c"] (1 row) SELECT * FROM age_split('a+b+c', '+'); - results --------------------- + age_split +----------------- ["a", "b", "c"] (1 row) + -- -- replace() --