This is an automated email from the ASF dual-hosted git repository. mtaha pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/age.git
The following commit(s) were added to refs/heads/master by this push: new 2803ffa7 Fix issue 2201: unexpected empty string behavior (#2203) 2803ffa7 is described below commit 2803ffa7801f37a0d02a22044150e04b2845d43a Author: John Gemignani <jrgemign...@gmail.com> AuthorDate: Wed Aug 13 08:13:29 2025 -0700 Fix issue 2201: unexpected empty string behavior (#2203) This PR fixes the issue of some string functions returning NULL instead of the empty string. The string functions affected and corrected are - reverse, toupper, tolower, rtrim, ltrim, trim, right, left, substring, and replace. Added additional regression tests. Corrected previous tests. modified: regress/expected/expr.out modified: regress/sql/expr.sql modified: src/backend/utils/adt/agtype.c --- regress/expected/expr.out | 245 +++++++++++++++++++++++++++++++++++------ regress/sql/expr.sql | 80 ++++++++++++-- src/backend/utils/adt/agtype.c | 42 ------- 3 files changed, 281 insertions(+), 86 deletions(-) diff --git a/regress/expected/expr.out b/regress/expected/expr.out index 76eecbe0..980172da 100644 --- a/regress/expected/expr.out +++ b/regress/expected/expr.out @@ -3927,6 +3927,41 @@ SELECT * FROM age_reverse('gnirts a si siht'::cstring); "this is a string" (1 row) +-- should return empty string +SELECT * FROM age_reverse(''); + age_reverse +------------- + "" +(1 row) + +SELECT * FROM age_reverse(''::text); + age_reverse +------------- + "" +(1 row) + +SELECT * FROM age_reverse(''::cstring); + age_reverse +------------- + "" +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN reverse('') +$$) AS (result agtype); + result +-------- + "" +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN reverse("") +$$) AS (result agtype); + result +-------- + "" +(1 row) + -- should return null SELECT * FROM cypher('expr', $$ RETURN reverse(null) @@ -4104,6 +4139,75 @@ SELECT * FROM age_tolower('CSTRING'::cstring); "cstring" (1 row) +-- should return empty string +SELECT * FROM age_toupper(''); + age_toupper +------------- + "" +(1 row) + +SELECT * FROM age_toupper(''::text); + age_toupper +------------- + "" +(1 row) + +SELECT * FROM age_toupper(''::cstring); + age_toupper +------------- + "" +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN toupper('') +$$) AS (result agtype); + result +-------- + "" +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN toupper("") +$$) AS (result agtype); + result +-------- + "" +(1 row) + +SELECT * FROM age_tolower(''); + age_tolower +------------- + "" +(1 row) + +SELECT * FROM age_tolower(''::text); + age_tolower +------------- + "" +(1 row) + +SELECT * FROM age_tolower(''::cstring); + age_tolower +------------- + "" +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN tolower('') +$$) AS (result agtype); + result +-------- + "" +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN tolower("") +$$) AS (result agtype); + result +-------- + "" +(1 row) + -- should return null SELECT * FROM cypher('expr', $$ RETURN toUpper(null) @@ -4211,6 +4315,73 @@ SELECT * FROM age_trim(' string '); "string" (1 row) +-- should return empty string +SELECT * FROM cypher('expr', $$ + RETURN lTrim('') +$$) AS (results agtype); + results +--------- + "" +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN rTrim('') +$$) AS (results agtype); + results +--------- + "" +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN trim('') +$$) AS (results agtype); + results +--------- + "" +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN lTrim("") +$$) AS (results agtype); + results +--------- + "" +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN rTrim("") +$$) AS (results agtype); + results +--------- + "" +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN trim("") +$$) AS (results agtype); + results +--------- + "" +(1 row) + +SELECT * FROM age_ltrim(''); + age_ltrim +----------- + "" +(1 row) + +SELECT * FROM age_rtrim(''); + age_rtrim +----------- + "" +(1 row) + +SELECT * FROM age_trim(''); + age_trim +---------- + "" +(1 row) + -- should return null SELECT * FROM cypher('expr', $$ RETURN lTrim(null) @@ -4322,15 +4493,16 @@ $$) AS (results agtype); "123" (1 row) --- should return null +-- should return empty string SELECT * FROM cypher('expr', $$ RETURN left("123456789", 0) $$) AS (results agtype); results --------- - + "" (1 row) +-- should return null SELECT * FROM cypher('expr', $$ RETURN left(null, 1) $$) AS (results agtype); @@ -4401,15 +4573,16 @@ $$) AS (results agtype); "789" (1 row) --- should return null +-- should return empty string SELECT * FROM cypher('expr', $$ RETURN right("123456789", 0) $$) AS (results agtype); results --------- - + "" (1 row) +-- should return null SELECT * FROM cypher('expr', $$ RETURN right(null, 1) $$) AS (results agtype); @@ -4508,6 +4681,13 @@ SELECT * FROM age_substring('0123456789', 1); "123456789" (1 row) +-- should return empty string +SELECT * FROM age_substring('0123456789', 0, 0); + age_substring +--------------- + "" +(1 row) + -- should return null SELECT * FROM cypher('expr', $$ RETURN substring(null, null, null) @@ -4747,33 +4927,52 @@ $$) AS (results agtype); "ababab" (1 row) --- should return null +-- should return empty string SELECT * FROM cypher('expr', $$ - RETURN replace(null, null, null) + RETURN replace("", "", "") $$) AS (results agtype); results --------- - + "" (1 row) SELECT * FROM cypher('expr', $$ - RETURN replace("Hello", null, null) + RETURN replace("Hello", "Hello", "") $$) AS (results agtype); results --------- - + "" (1 row) SELECT * FROM cypher('expr', $$ - RETURN replace("Hello", "", null) + RETURN replace("", "Hello", "Mellow") $$) AS (results agtype); results --------- - + "" +(1 row) + +SELECT * FROM age_replace('', '', ''); + age_replace +------------- + "" (1 row) +SELECT * FROM age_replace('Hello', 'Hello', ''); + age_replace +------------- + "" +(1 row) + +SELECT * FROM age_replace('', 'Hello', 'Mellow'); + age_replace +------------- + "" +(1 row) + +-- should return null SELECT * FROM cypher('expr', $$ - RETURN replace("", "", "") + RETURN replace(null, null, null) $$) AS (results agtype); results --------- @@ -4781,7 +4980,7 @@ $$) AS (results agtype); (1 row) SELECT * FROM cypher('expr', $$ - RETURN replace("Hello", "Hello", "") + RETURN replace("Hello", null, null) $$) AS (results agtype); results --------- @@ -4789,7 +4988,7 @@ $$) AS (results agtype); (1 row) SELECT * FROM cypher('expr', $$ - RETURN replace("", "Hello", "Mellow") + RETURN replace("Hello", "", null) $$) AS (results agtype); results --------- @@ -4814,24 +5013,6 @@ SELECT * FROM age_replace('Hello', '', null); (1 row) -SELECT * FROM age_replace('', '', ''); - age_replace -------------- - -(1 row) - -SELECT * FROM age_replace('Hello', 'Hello', ''); - age_replace -------------- - -(1 row) - -SELECT * FROM age_replace('', 'Hello', 'Mellow'); - age_replace -------------- - -(1 row) - -- should fail SELECT * FROM cypher('expr', $$ RETURN replace() diff --git a/regress/sql/expr.sql b/regress/sql/expr.sql index a0cf1b02..16987b81 100644 --- a/regress/sql/expr.sql +++ b/regress/sql/expr.sql @@ -1669,6 +1669,16 @@ $$) AS (results agtype); SELECT * FROM age_reverse('gnirts a si siht'); SELECT * FROM age_reverse('gnirts a si siht'::text); SELECT * FROM age_reverse('gnirts a si siht'::cstring); +-- should return empty string +SELECT * FROM age_reverse(''); +SELECT * FROM age_reverse(''::text); +SELECT * FROM age_reverse(''::cstring); +SELECT * FROM cypher('expr', $$ + RETURN reverse('') +$$) AS (result agtype); +SELECT * FROM cypher('expr', $$ + RETURN reverse("") +$$) AS (result agtype); -- should return null SELECT * FROM cypher('expr', $$ RETURN reverse(null) @@ -1742,6 +1752,25 @@ SELECT * FROM age_toupper('text'::text); SELECT * FROM age_toupper('cstring'::cstring); SELECT * FROM age_tolower('TEXT'::text); SELECT * FROM age_tolower('CSTRING'::cstring); +-- should return empty string +SELECT * FROM age_toupper(''); +SELECT * FROM age_toupper(''::text); +SELECT * FROM age_toupper(''::cstring); +SELECT * FROM cypher('expr', $$ + RETURN toupper('') +$$) AS (result agtype); +SELECT * FROM cypher('expr', $$ + RETURN toupper("") +$$) AS (result agtype); +SELECT * FROM age_tolower(''); +SELECT * FROM age_tolower(''::text); +SELECT * FROM age_tolower(''::cstring); +SELECT * FROM cypher('expr', $$ + RETURN tolower('') +$$) AS (result agtype); +SELECT * FROM cypher('expr', $$ + RETURN tolower("") +$$) AS (result agtype); -- should return null SELECT * FROM cypher('expr', $$ RETURN toUpper(null) @@ -1783,6 +1812,28 @@ $$) AS (results agtype); SELECT * FROM age_ltrim(' string '); SELECT * FROM age_rtrim(' string '); SELECT * FROM age_trim(' string '); +-- should return empty string +SELECT * FROM cypher('expr', $$ + RETURN lTrim('') +$$) AS (results agtype); +SELECT * FROM cypher('expr', $$ + RETURN rTrim('') +$$) AS (results agtype); +SELECT * FROM cypher('expr', $$ + RETURN trim('') +$$) AS (results agtype); +SELECT * FROM cypher('expr', $$ + RETURN lTrim("") +$$) AS (results agtype); +SELECT * FROM cypher('expr', $$ + RETURN rTrim("") +$$) AS (results agtype); +SELECT * FROM cypher('expr', $$ + RETURN trim("") +$$) AS (results agtype); +SELECT * FROM age_ltrim(''); +SELECT * FROM age_rtrim(''); +SELECT * FROM age_trim(''); -- should return null SELECT * FROM cypher('expr', $$ RETURN lTrim(null) @@ -1829,10 +1880,11 @@ $$) AS (results agtype); SELECT * FROM cypher('expr', $$ RETURN left("123456789", 3) $$) AS (results agtype); --- should return null +-- should return empty string SELECT * FROM cypher('expr', $$ RETURN left("123456789", 0) $$) AS (results agtype); +-- should return null SELECT * FROM cypher('expr', $$ RETURN left(null, 1) $$) AS (results agtype); @@ -1861,10 +1913,11 @@ $$) AS (results agtype); SELECT * FROM cypher('expr', $$ RETURN right("123456789", 3) $$) AS (results agtype); --- should return null +-- should return empty string SELECT * FROM cypher('expr', $$ RETURN right("123456789", 0) $$) AS (results agtype); +-- should return null SELECT * FROM cypher('expr', $$ RETURN right(null, 1) $$) AS (results agtype); @@ -1901,6 +1954,8 @@ SELECT * FROM cypher('expr', $$ $$) AS (results agtype); SELECT * FROM age_substring('0123456789', 3, 2); SELECT * FROM age_substring('0123456789', 1); +-- should return empty string +SELECT * FROM age_substring('0123456789', 0, 0); -- should return null SELECT * FROM cypher('expr', $$ RETURN substring(null, null, null) @@ -2002,31 +2057,32 @@ $$) AS (results agtype); SELECT * FROM cypher('expr', $$ RETURN replace("ababab", "ab", "ab") $$) AS (results agtype); --- should return null +-- should return empty string SELECT * FROM cypher('expr', $$ - RETURN replace(null, null, null) + RETURN replace("", "", "") $$) AS (results agtype); SELECT * FROM cypher('expr', $$ - RETURN replace("Hello", null, null) + RETURN replace("Hello", "Hello", "") $$) AS (results agtype); SELECT * FROM cypher('expr', $$ - RETURN replace("Hello", "", null) + RETURN replace("", "Hello", "Mellow") $$) AS (results agtype); +SELECT * FROM age_replace('', '', ''); +SELECT * FROM age_replace('Hello', 'Hello', ''); +SELECT * FROM age_replace('', 'Hello', 'Mellow'); +-- should return null SELECT * FROM cypher('expr', $$ - RETURN replace("", "", "") + RETURN replace(null, null, null) $$) AS (results agtype); SELECT * FROM cypher('expr', $$ - RETURN replace("Hello", "Hello", "") + RETURN replace("Hello", null, null) $$) AS (results agtype); SELECT * FROM cypher('expr', $$ - RETURN replace("", "Hello", "Mellow") + RETURN replace("Hello", "", null) $$) AS (results agtype); SELECT * FROM age_replace(null, null, null); SELECT * FROM age_replace('Hello', null, null); SELECT * FROM age_replace('Hello', '', null); -SELECT * FROM age_replace('', '', ''); -SELECT * FROM age_replace('Hello', 'Hello', ''); -SELECT * FROM age_replace('', 'Hello', 'Mellow'); -- should fail SELECT * FROM cypher('expr', $$ RETURN replace() diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index d26929d3..17e08353 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -7691,12 +7691,6 @@ Datum age_reverse(PG_FUNCTION_ARGS) string = text_to_cstring(text_string); string_len = strlen(string); - /* if we have an empty string, return null */ - if (string_len == 0) - { - PG_RETURN_NULL(); - } - /* build the result */ agtv_result.type = AGTV_STRING; agtv_result.val.string.val = string; @@ -7776,10 +7770,6 @@ Datum age_toupper(PG_FUNCTION_ARGS) agtv_value->type))); } - /* if we have an empty string, return null */ - if (string_len == 0) - PG_RETURN_NULL(); - /* allocate the new string */ result = palloc0(string_len); @@ -7866,10 +7856,6 @@ Datum age_tolower(PG_FUNCTION_ARGS) agtv_value->type))); } - /* if we have an empty string, return null */ - if (string_len == 0) - PG_RETURN_NULL(); - /* allocate the new string */ result = palloc0(string_len); @@ -7964,10 +7950,6 @@ Datum age_rtrim(PG_FUNCTION_ARGS) string = text_to_cstring(text_string); string_len = strlen(string); - /* if we have an empty string, return null */ - if (string_len == 0) - PG_RETURN_NULL(); - /* build the result */ agtv_result.type = AGTV_STRING; agtv_result.val.string.val = string; @@ -8055,10 +8037,6 @@ Datum age_ltrim(PG_FUNCTION_ARGS) string = text_to_cstring(text_string); string_len = strlen(string); - /* if we have an empty string, return null */ - if (string_len == 0) - PG_RETURN_NULL(); - /* build the result */ agtv_result.type = AGTV_STRING; agtv_result.val.string.val = string; @@ -8146,10 +8124,6 @@ Datum age_trim(PG_FUNCTION_ARGS) string = text_to_cstring(text_string); string_len = strlen(string); - /* if we have an empty string, return null */ - if (string_len == 0) - PG_RETURN_NULL(); - /* build the result */ agtv_result.type = AGTV_STRING; agtv_result.val.string.val = string; @@ -8287,10 +8261,6 @@ Datum age_right(PG_FUNCTION_ARGS) string = text_to_cstring(text_string); string_len = strlen(string); - /* if we have an empty string, return null */ - if (string_len == 0) - PG_RETURN_NULL(); - /* build the result */ agtv_result.type = AGTV_STRING; agtv_result.val.string.val = string; @@ -8428,10 +8398,6 @@ Datum age_left(PG_FUNCTION_ARGS) string = text_to_cstring(text_string); string_len = strlen(string); - /* if we have an empty string, return null */ - if (string_len == 0) - PG_RETURN_NULL(); - /* build the result */ agtv_result.type = AGTV_STRING; agtv_result.val.string.val = string; @@ -8597,10 +8563,6 @@ Datum age_substring(PG_FUNCTION_ARGS) string = text_to_cstring(text_string); string_len = strlen(string); - /* if we have an empty string, return null */ - if (string_len == 0) - PG_RETURN_NULL(); - /* build the result */ agtv_result.type = AGTV_STRING; agtv_result.val.string.val = string; @@ -8855,10 +8817,6 @@ Datum age_replace(PG_FUNCTION_ARGS) string = text_to_cstring(text_result); string_len = strlen(string); - /* if we have an empty string, return null */ - if (string_len == 0) - PG_RETURN_NULL(); - /* build the result */ agtv_result.type = AGTV_STRING; agtv_result.val.string.val = string;