This is an automated email from the ASF dual-hosted git repository.

mtaha pushed a commit to branch PG15
in repository https://gitbox.apache.org/repos/asf/age.git

commit a2e608cfe704ba2046de1a5917c7a2b0c2d4bc0e
Author: John Gemignani <[email protected]>
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 106ef341..fd4526ef 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 1152dc3a..13a3375e 100644
--- a/regress/sql/expr.sql
+++ b/regress/sql/expr.sql
@@ -1668,6 +1668,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)
@@ -1741,6 +1751,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)
@@ -1782,6 +1811,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)
@@ -1828,10 +1879,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);
@@ -1860,10 +1912,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);
@@ -1900,6 +1953,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)
@@ -2001,31 +2056,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 545e6584..24799a54 100644
--- a/src/backend/utils/adt/agtype.c
+++ b/src/backend/utils/adt/agtype.c
@@ -7685,12 +7685,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;
@@ -7770,10 +7764,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);
 
@@ -7860,10 +7850,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);
 
@@ -7958,10 +7944,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;
@@ -8049,10 +8031,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;
@@ -8140,10 +8118,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;
@@ -8281,10 +8255,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;
@@ -8422,10 +8392,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;
@@ -8591,10 +8557,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;
@@ -8849,10 +8811,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;

Reply via email to