You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@age.apache.org by mt...@apache.org on 2025/10/17 18:30:11 UTC
(age) 06/07: Fix issue 2201: unexpected empty string behavior (#2203)
This is an automated email from the ASF dual-hosted git repository.
mtaha pushed a commit to branch PG14
in repository https://gitbox.apache.org/repos/asf/age.git
commit 4608cd4969650fac0307281dae549472882e55f1
Author: John Gemignani <jr...@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 660eb132..a9313ba0 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 c3ddee86..3ce19204 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 d044c05f..ab227e07 100644
--- a/src/backend/utils/adt/agtype.c
+++ b/src/backend/utils/adt/agtype.c
@@ -7647,12 +7647,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;
@@ -7732,10 +7726,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);
@@ -7822,10 +7812,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);
@@ -7920,10 +7906,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;
@@ -8011,10 +7993,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;
@@ -8102,10 +8080,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;
@@ -8243,10 +8217,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;
@@ -8384,10 +8354,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;
@@ -8553,10 +8519,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;
@@ -8811,10 +8773,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;