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;

Reply via email to