This is an automated email from the ASF dual-hosted git repository.
jgemignani pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-age.git
The following commit(s) were added to refs/heads/master by this push:
new f569166 Add openCypher string function replace().
f569166 is described below
commit f56916652ca373fcceaf80729c40a16fdbbb0d28
Author: John Gemignani <[email protected]>
AuthorDate: Mon Aug 17 15:29:29 2020 -0700
Add openCypher string function replace().
Added openCypher string function replace().
Added regression tests.
---
age--0.2.0.sql | 7 ++
regress/expected/cypher_create.out | 16 ++--
regress/expected/expr.out | 168 +++++++++++++++++++++++++++++++++++++
regress/sql/expr.sql | 65 ++++++++++++++
src/backend/parser/cypher_expr.c | 4 +-
src/backend/utils/adt/agtype.c | 111 ++++++++++++++++++++++++
6 files changed, 362 insertions(+), 9 deletions(-)
diff --git a/age--0.2.0.sql b/age--0.2.0.sql
index e4ffe8b..bce57be 100644
--- a/age--0.2.0.sql
+++ b/age--0.2.0.sql
@@ -986,6 +986,13 @@ STABLE
PARALLEL SAFE
AS 'MODULE_PATHNAME';
+CREATE FUNCTION replace(variadic "any")
+RETURNS agtype
+LANGUAGE c
+STABLE
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
--
-- function for typecasting an agtype value to another agtype value
--
diff --git a/regress/expected/cypher_create.out
b/regress/expected/cypher_create.out
index fd0ad5c..47190eb 100644
--- a/regress/expected/cypher_create.out
+++ b/regress/expected/cypher_create.out
@@ -375,14 +375,14 @@ SELECT * FROM cypher_create.e_var;
SELECT * FROM ag_label;
name | graph | id | kind | relation
------------------+-------+----+------+--------------------------------
- _ag_label_vertex | 17027 | 1 | v | cypher_create._ag_label_vertex
- _ag_label_edge | 17027 | 2 | e | cypher_create._ag_label_edge
- v | 17027 | 3 | v | cypher_create.v
- e | 17027 | 4 | e | cypher_create.e
- n_var | 17027 | 5 | v | cypher_create.n_var
- e_var | 17027 | 6 | e | cypher_create.e_var
- n_other_node | 17027 | 7 | v | cypher_create.n_other_node
- b_var | 17027 | 8 | e | cypher_create.b_var
+ _ag_label_vertex | 17029 | 1 | v | cypher_create._ag_label_vertex
+ _ag_label_edge | 17029 | 2 | e | cypher_create._ag_label_edge
+ v | 17029 | 3 | v | cypher_create.v
+ e | 17029 | 4 | e | cypher_create.e
+ n_var | 17029 | 5 | v | cypher_create.n_var
+ e_var | 17029 | 6 | e | cypher_create.e_var
+ n_other_node | 17029 | 7 | v | cypher_create.n_other_node
+ b_var | 17029 | 8 | e | cypher_create.b_var
(8 rows)
--Validate every vertex has the correct label
diff --git a/regress/expected/expr.out b/regress/expected/expr.out
index af0279d..6c2aacd 100644
--- a/regress/expected/expr.out
+++ b/regress/expected/expr.out
@@ -2789,6 +2789,174 @@ LINE 1: SELECT * FROM split();
^
HINT: No function matches the given name and argument types. You might need
to add explicit type casts.
--
+-- replace()
+--
+SELECT * FROM cypher('expr', $$
+ RETURN replace("Hello", "lo", "p")
+$$) AS (results agtype);
+ results
+---------
+ "Help"
+(1 row)
+
+SELECT * FROM cypher('expr', $$
+ RETURN replace("Hello", "hello", "Good bye")
+$$) AS (results agtype);
+ results
+---------
+ "Hello"
+(1 row)
+
+SELECT * FROM cypher('expr', $$
+ RETURN replace("abcabcabc", "abc", "a")
+$$) AS (results agtype);
+ results
+---------
+ "aaa"
+(1 row)
+
+SELECT * FROM cypher('expr', $$
+ RETURN replace("abcabcabc", "ab", "")
+$$) AS (results agtype);
+ results
+---------
+ "ccc"
+(1 row)
+
+SELECT * FROM cypher('expr', $$
+ RETURN replace("ababab", "ab", "ab")
+$$) AS (results agtype);
+ results
+----------
+ "ababab"
+(1 row)
+
+-- should return null
+SELECT * FROM cypher('expr', $$
+ RETURN replace(null, null, null)
+$$) AS (results agtype);
+ results
+---------
+
+(1 row)
+
+SELECT * FROM cypher('expr', $$
+ RETURN replace("Hello", null, null)
+$$) AS (results agtype);
+ results
+---------
+
+(1 row)
+
+SELECT * FROM cypher('expr', $$
+ RETURN replace("Hello", "", null)
+$$) AS (results agtype);
+ results
+---------
+
+(1 row)
+
+SELECT * FROM cypher('expr', $$
+ RETURN replace("", "", "")
+$$) AS (results agtype);
+ results
+---------
+
+(1 row)
+
+SELECT * FROM cypher('expr', $$
+ RETURN replace("Hello", "Hello", "")
+$$) AS (results agtype);
+ results
+---------
+
+(1 row)
+
+SELECT * FROM cypher('expr', $$
+ RETURN replace("", "Hello", "Mellow")
+$$) AS (results agtype);
+ results
+---------
+
+(1 row)
+
+SELECT * FROM replace(null, null, null);
+ replace
+---------
+
+(1 row)
+
+SELECT * FROM replace('Hello', null, null);
+ replace
+---------
+
+(1 row)
+
+SELECT * FROM replace('Hello', '', null);
+ replace
+---------
+
+(1 row)
+
+SELECT * FROM replace('', '', '');
+ replace
+---------
+
+(1 row)
+
+SELECT * FROM replace('Hello', 'Hello', '');
+ replace
+---------
+
+(1 row)
+
+SELECT * FROM replace('', 'Hello', 'Mellow');
+ replace
+---------
+
+(1 row)
+
+-- should fail
+SELECT * FROM cypher('expr', $$
+ RETURN replace()
+$$) AS (results agtype);
+ERROR: unrecognized or unsupported function
+LINE 1: SELECT * FROM cypher('expr', $$
+ ^
+SELECT * FROM cypher('expr', $$
+ RETURN replace("Hello")
+$$) AS (results agtype);
+ERROR: invalid number of input parameters for replace()
+LINE 1: SELECT * FROM cypher('expr', $$
+ ^
+SELECT * FROM cypher('expr', $$
+ RETURN replace("Hello", null)
+$$) AS (results agtype);
+ERROR: invalid number of input parameters for replace()
+LINE 1: SELECT * FROM cypher('expr', $$
+ ^
+SELECT * FROM cypher('expr', $$
+ RETURN replace("Hello", "e", 1)
+$$) AS (results agtype);
+ERROR: replace() unsuppoted argument agtype 3
+SELECT * FROM cypher('expr', $$
+ RETURN replace("Hello", 1, "e")
+$$) AS (results agtype);
+ERROR: replace() unsuppoted argument agtype 3
+SELECT * FROM replace();
+ERROR: function replace() does not exist
+LINE 1: SELECT * FROM replace();
+ ^
+HINT: No function matches the given name and argument types. You might need
to add explicit type casts.
+SELECT * FROM replace(null);
+ERROR: replace() invalid number of arguments
+SELECT * FROM replace(null, null);
+ERROR: replace() invalid number of arguments
+SELECT * FROM replace('Hello', 'e', 1);
+ERROR: replace() unsuppoted argument type 23
+SELECT * FROM replace('Hello', 1, 'E');
+ERROR: replace() unsuppoted argument type 23
+--
-- Cleanup
--
SELECT * FROM drop_graph('expr', true);
diff --git a/regress/sql/expr.sql b/regress/sql/expr.sql
index e4a00f5..4ad18f3 100644
--- a/regress/sql/expr.sql
+++ b/regress/sql/expr.sql
@@ -1205,6 +1205,71 @@ SELECT * FROM split('a,b,c,d,e,f');
SELECT * FROM split();
--
+-- replace()
+--
+SELECT * FROM cypher('expr', $$
+ RETURN replace("Hello", "lo", "p")
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+ RETURN replace("Hello", "hello", "Good bye")
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+ RETURN replace("abcabcabc", "abc", "a")
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+ RETURN replace("abcabcabc", "ab", "")
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+ RETURN replace("ababab", "ab", "ab")
+$$) AS (results agtype);
+-- should return null
+SELECT * FROM cypher('expr', $$
+ RETURN replace(null, null, null)
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+ RETURN replace("Hello", null, null)
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+ RETURN replace("Hello", "", null)
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+ RETURN replace("", "", "")
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+ RETURN replace("Hello", "Hello", "")
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+ RETURN replace("", "Hello", "Mellow")
+$$) AS (results agtype);
+SELECT * FROM replace(null, null, null);
+SELECT * FROM replace('Hello', null, null);
+SELECT * FROM replace('Hello', '', null);
+SELECT * FROM replace('', '', '');
+SELECT * FROM replace('Hello', 'Hello', '');
+SELECT * FROM replace('', 'Hello', 'Mellow');
+-- should fail
+SELECT * FROM cypher('expr', $$
+ RETURN replace()
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+ RETURN replace("Hello")
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+ RETURN replace("Hello", null)
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+ RETURN replace("Hello", "e", 1)
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+ RETURN replace("Hello", 1, "e")
+$$) AS (results agtype);
+SELECT * FROM replace();
+SELECT * FROM replace(null);
+SELECT * FROM replace(null, null);
+SELECT * FROM replace('Hello', 'e', 1);
+SELECT * FROM replace('Hello', 1, 'E');
+
+--
-- Cleanup
--
SELECT * FROM drop_graph('expr', true);
diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c
index a48f03d..5860c3e 100644
--- a/src/backend/parser/cypher_expr.c
+++ b/src/backend/parser/cypher_expr.c
@@ -67,6 +67,7 @@
#define FUNC_LSUBSTR {"left", "l_substr", ANYOID, ANYOID, 0,
AGTYPEOID, 2, 1, false}
#define FUNC_BSUBSTR {"substring", "b_substr", ANYOID, ANYOID,
ANYOID, AGTYPEOID, -1, 1, false}
#define FUNC_SPLIT {"split", "split", ANYOID, ANYOID, 0,
AGTYPEOID, 2, 1, false}
+#define FUNC_REPLACE {"replace", "replace", ANYOID, ANYOID, 0,
AGTYPEOID, 3, 1, false}
/* supported functions */
#define SUPPORTED_FUNCTIONS {FUNC_TYPE, FUNC_ENDNODE, FUNC_HEAD, FUNC_ID, \
@@ -76,7 +77,8 @@
FUNC_EXISTS, FUNC_TOSTRING, FUNC_REVERSE, \
FUNC_TOUPPER, FUNC_TOLOWER, FUNC_LTRIM, \
FUNC_RTRIM, FUNC_BTRIM, FUNC_RSUBSTR, \
- FUNC_LSUBSTR, FUNC_BSUBSTR, FUNC_SPLIT}
+ FUNC_LSUBSTR, FUNC_BSUBSTR, FUNC_SPLIT, \
+ FUNC_REPLACE}
/* structure for supported function signatures */
typedef struct function_signature
diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c
index e2a5291..942ab5d 100644
--- a/src/backend/utils/adt/agtype.c
+++ b/src/backend/utils/adt/agtype.c
@@ -5500,3 +5500,114 @@ Datum split(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result));
}
+
+PG_FUNCTION_INFO_V1(replace);
+
+Datum replace(PG_FUNCTION_ARGS)
+{
+ int nargs;
+ Datum *args;
+ Datum arg;
+ bool *nulls;
+ Oid *types;
+ agtype_value agtv_result;
+ text *param = NULL;
+ text *text_string = NULL;
+ text *text_search = NULL;
+ text *text_replace = NULL;
+ text *text_result = NULL;
+ char *string = NULL;
+ int string_len;
+ Oid type;
+ int i;
+
+ /* extract argument values */
+ nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls);
+
+ /* check number of args */
+ if (nargs != 3)
+ ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("replace() invalid number of arguments")));
+
+ /* check for a null string, search, and replace */
+ if (nargs < 0 || nulls[0] || nulls[1] || nulls[2])
+ PG_RETURN_NULL();
+
+ /*
+ * replace() supports text, cstring, or the agtype string input for the
+ * string and delimiter values
+ */
+
+ for (i = 0; i < 3; i++)
+ {
+ arg = args[i];
+ type = types[i];
+
+ if (type != AGTYPEOID)
+ {
+ if (type == CSTRINGOID)
+ param = cstring_to_text(DatumGetCString(arg));
+ else if (type == TEXTOID)
+ param = DatumGetTextPP(arg);
+ else
+ ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("replace() unsuppoted argument type %d",
+ type)));
+ }
+ else
+ {
+ agtype *agt_arg;
+ agtype_value *agtv_value;
+
+ /* get the agtype argument */
+ agt_arg = DATUM_GET_AGTYPE_P(arg);
+
+ if (!AGT_ROOT_IS_SCALAR(agt_arg))
+ ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("replace() only supports scalar
arguments")));
+
+ agtv_value = get_ith_agtype_value_from_container(&agt_arg->root,
0);
+
+ /* check for agtype null */
+ if (agtv_value->type == AGTV_NULL)
+ PG_RETURN_NULL();
+ if (agtv_value->type == AGTV_STRING)
+ param = cstring_to_text_with_len(agtv_value->val.string.val,
+ agtv_value->val.string.len);
+ else
+ ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("replace() unsuppoted argument agtype
%d",
+ agtv_value->type)));
+ }
+ if (i == 0)
+ text_string = param;
+ if (i == 1)
+ text_search = param;
+ if (i == 2)
+ text_replace = param;
+ }
+
+ /*
+ * We need the strings as a text strings so that we can let PG deal with
+ * multibyte characters in the string.
+ */
+ text_result = DatumGetTextPP(DirectFunctionCall3(replace_text,
+
PointerGetDatum(text_string),
+
PointerGetDatum(text_search),
+
PointerGetDatum(text_replace)));
+
+ /* convert it back to a cstring */
+ 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;
+ agtv_result.val.string.len = string_len;
+
+ PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result));
+}