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 abf969a  Add openCypher functions left(), right(), & substring()
abf969a is described below

commit abf969a5ca0c3a95b41c4ca4d7122f553e3d3d65
Author: John Gemignani <[email protected]>
AuthorDate: Mon Aug 10 16:22:40 2020 -0700

    Add openCypher functions left(), right(), & substring()
    
    Added the openCypher functions left(), right(), & substring().
    
    Added regression tests.
---
 age--0.2.0.sql                     |  21 ++
 regress/expected/cypher_create.out |  16 +-
 regress/expected/expr.out          | 274 ++++++++++++++++++++++
 regress/sql/expr.sql               | 112 +++++++++
 src/backend/parser/cypher_expr.c   |  15 +-
 src/backend/utils/adt/agtype.c     | 451 +++++++++++++++++++++++++++++++++++++
 6 files changed, 878 insertions(+), 11 deletions(-)

diff --git a/age--0.2.0.sql b/age--0.2.0.sql
index e3faff5..14b079d 100644
--- a/age--0.2.0.sql
+++ b/age--0.2.0.sql
@@ -958,6 +958,27 @@ RETURNS NULL ON NULL INPUT
 PARALLEL SAFE
 AS 'MODULE_PATHNAME';
 
+CREATE FUNCTION r_substr(variadic "any")
+RETURNS agtype
+LANGUAGE c
+STABLE
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION l_substr(variadic "any")
+RETURNS agtype
+LANGUAGE c
+STABLE
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION b_substr(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 d60bc70..f23e80d 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 | 17019 |  1 | v    | cypher_create._ag_label_vertex
- _ag_label_edge   | 17019 |  2 | e    | cypher_create._ag_label_edge
- v                | 17019 |  3 | v    | cypher_create.v
- e                | 17019 |  4 | e    | cypher_create.e
- n_var            | 17019 |  5 | v    | cypher_create.n_var
- e_var            | 17019 |  6 | e    | cypher_create.e_var
- n_other_node     | 17019 |  7 | v    | cypher_create.n_other_node
- b_var            | 17019 |  8 | e    | cypher_create.b_var
+ _ag_label_vertex | 17025 |  1 | v    | cypher_create._ag_label_vertex
+ _ag_label_edge   | 17025 |  2 | e    | cypher_create._ag_label_edge
+ v                | 17025 |  3 | v    | cypher_create.v
+ e                | 17025 |  4 | e    | cypher_create.e
+ n_var            | 17025 |  5 | v    | cypher_create.n_var
+ e_var            | 17025 |  6 | e    | cypher_create.e_var
+ n_other_node     | 17025 |  7 | v    | cypher_create.n_other_node
+ b_var            | 17025 |  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 3c6be61..8626e47 100644
--- a/regress/expected/expr.out
+++ b/regress/expected/expr.out
@@ -2389,6 +2389,280 @@ LINE 1: SELECT * FROM b_trim();
                       ^
 HINT:  No function matches the given name and argument types. You might need 
to add explicit type casts.
 --
+-- left(), right(), & substring()
+-- left()
+SELECT * FROM cypher('expr', $$
+    RETURN left("123456789", 1)
+$$) AS (results agtype);
+ results 
+---------
+ "1"
+(1 row)
+
+SELECT * FROM cypher('expr', $$
+    RETURN left("123456789", 3)
+$$) AS (results agtype);
+ results 
+---------
+ "123"
+(1 row)
+
+-- should return null
+SELECT * FROM cypher('expr', $$
+    RETURN left("123456789", 0)
+$$) AS (results agtype);
+ results 
+---------
+ 
+(1 row)
+
+SELECT * FROM cypher('expr', $$
+    RETURN left(null, 1)
+$$) AS (results agtype);
+ results 
+---------
+ 
+(1 row)
+
+SELECT * FROM cypher('expr', $$
+    RETURN left(null, null)
+$$) AS (results agtype);
+ results 
+---------
+ 
+(1 row)
+
+SELECT * FROM l_substr(null, 1);
+ l_substr 
+----------
+ 
+(1 row)
+
+SELECT * FROM l_substr(null, null);
+ l_substr 
+----------
+ 
+(1 row)
+
+-- should fail
+SELECT * FROM cypher('expr', $$
+    RETURN left("123456789", null)
+$$) AS (results agtype);
+ERROR:  left() length parameter cannot be null
+SELECT * FROM cypher('expr', $$
+    RETURN left("123456789", -1)
+$$) AS (results agtype);
+ERROR:  left() negative values are not supported for length
+SELECT * FROM cypher('expr', $$
+    RETURN left()
+$$) AS (results agtype);
+ERROR:  unrecognized or unsupported function
+LINE 1: SELECT * FROM cypher('expr', $$
+                                      ^
+SELECT * FROM l_substr('123456789', null);
+ERROR:  left() length parameter cannot be null
+SELECT * FROM l_substr('123456789', -1);
+ERROR:  left() negative values are not supported for length
+SELECT * FROM l_substr();
+ERROR:  function l_substr() does not exist
+LINE 1: SELECT * FROM l_substr();
+                      ^
+HINT:  No function matches the given name and argument types. You might need 
to add explicit type casts.
+--right()
+SELECT * FROM cypher('expr', $$
+    RETURN right("123456789", 1)
+$$) AS (results agtype);
+ results 
+---------
+ "9"
+(1 row)
+
+SELECT * FROM cypher('expr', $$
+    RETURN right("123456789", 3)
+$$) AS (results agtype);
+ results 
+---------
+ "789"
+(1 row)
+
+-- should return null
+SELECT * FROM cypher('expr', $$
+    RETURN right("123456789", 0)
+$$) AS (results agtype);
+ results 
+---------
+ 
+(1 row)
+
+SELECT * FROM cypher('expr', $$
+    RETURN right(null, 1)
+$$) AS (results agtype);
+ results 
+---------
+ 
+(1 row)
+
+SELECT * FROM cypher('expr', $$
+    RETURN right(null, null)
+$$) AS (results agtype);
+ results 
+---------
+ 
+(1 row)
+
+SELECT * FROM r_substr(null, 1);
+ r_substr 
+----------
+ 
+(1 row)
+
+SELECT * FROM r_substr(null, null);
+ r_substr 
+----------
+ 
+(1 row)
+
+-- should fail
+SELECT * FROM cypher('expr', $$
+    RETURN right("123456789", null)
+$$) AS (results agtype);
+ERROR:  right() length parameter cannot be null
+SELECT * FROM cypher('expr', $$
+    RETURN right("123456789", -1)
+$$) AS (results agtype);
+ERROR:  right() negative values are not supported for length
+SELECT * FROM cypher('expr', $$
+    RETURN right()
+$$) AS (results agtype);
+ERROR:  unrecognized or unsupported function
+LINE 1: SELECT * FROM cypher('expr', $$
+                                      ^
+SELECT * FROM r_substr('123456789', null);
+ERROR:  right() length parameter cannot be null
+SELECT * FROM r_substr('123456789', -1);
+ERROR:  right() negative values are not supported for length
+SELECT * FROM r_substr();
+ERROR:  function r_substr() does not exist
+LINE 1: SELECT * FROM r_substr();
+                      ^
+HINT:  No function matches the given name and argument types. You might need 
to add explicit type casts.
+-- substring()
+SELECT * FROM cypher('expr', $$
+    RETURN substring("0123456789", 0, 1)
+$$) AS (results agtype);
+ results 
+---------
+ "0"
+(1 row)
+
+SELECT * FROM cypher('expr', $$
+    RETURN substring("0123456789", 1, 3)
+$$) AS (results agtype);
+ results 
+---------
+ "123"
+(1 row)
+
+SELECT * FROM cypher('expr', $$
+    RETURN substring("0123456789", 3)
+$$) AS (results agtype);
+  results  
+-----------
+ "3456789"
+(1 row)
+
+SELECT * FROM cypher('expr', $$
+    RETURN substring("0123456789", 0)
+$$) AS (results agtype);
+   results    
+--------------
+ "0123456789"
+(1 row)
+
+SELECT * FROM b_substr('0123456789', 3, 2);
+ b_substr 
+----------
+ "34"
+(1 row)
+
+SELECT * FROM b_substr('0123456789', 1);
+  b_substr   
+-------------
+ "123456789"
+(1 row)
+
+-- should return null
+SELECT * FROM cypher('expr', $$
+    RETURN substring(null, null, null)
+$$) AS (results agtype);
+ results 
+---------
+ 
+(1 row)
+
+SELECT * FROM cypher('expr', $$
+    RETURN substring(null, null)
+$$) AS (results agtype);
+ results 
+---------
+ 
+(1 row)
+
+SELECT * FROM cypher('expr', $$
+    RETURN substring(null, 1)
+$$) AS (results agtype);
+ results 
+---------
+ 
+(1 row)
+
+SELECT * FROM b_substr(null, null, null);
+ b_substr 
+----------
+ 
+(1 row)
+
+SELECT * FROM b_substr(null, null);
+ b_substr 
+----------
+ 
+(1 row)
+
+SELECT * FROM b_substr(null, 1);
+ b_substr 
+----------
+ 
+(1 row)
+
+-- should fail
+SELECT * FROM cypher('expr', $$
+    RETURN substring("123456789", null)
+$$) AS (results agtype);
+ERROR:  substring() offset or length cannot be null
+SELECT * FROM cypher('expr', $$
+    RETURN substring("123456789", 0, -1)
+$$) AS (results agtype);
+ERROR:  substring() negative values are not supported for offset or length
+SELECT * FROM cypher('expr', $$
+    RETURN substring("123456789", -1)
+$$) AS (results agtype);
+ERROR:  substring() negative values are not supported for offset or length
+SELECT * FROM cypher('expr', $$
+    RETURN substring("123456789")
+$$) AS (results agtype);
+ERROR:  substring() invalid number of arguments
+SELECT * FROM b_substr('123456789', null);
+ERROR:  substring() offset or length cannot be null
+SELECT * FROM b_substr('123456789', 0, -1);
+ERROR:  substring() negative values are not supported for offset or length
+SELECT * FROM b_substr('123456789', -1);
+ERROR:  substring() negative values are not supported for offset or length
+SELECT * FROM b_substr();
+ERROR:  function b_substr() does not exist
+LINE 1: SELECT * FROM b_substr();
+                      ^
+HINT:  No function matches the given name and argument types. You might need 
to add explicit type casts.
+--
 -- Cleanup
 --
 SELECT * FROM drop_graph('expr', true);
diff --git a/regress/sql/expr.sql b/regress/sql/expr.sql
index 0f205c5..6d3f38c 100644
--- a/regress/sql/expr.sql
+++ b/regress/sql/expr.sql
@@ -1041,6 +1041,118 @@ SELECT * FROM r_trim();
 SELECT * FROM b_trim();
 
 --
+-- left(), right(), & substring()
+-- left()
+SELECT * FROM cypher('expr', $$
+    RETURN left("123456789", 1)
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+    RETURN left("123456789", 3)
+$$) AS (results agtype);
+-- should return null
+SELECT * FROM cypher('expr', $$
+    RETURN left("123456789", 0)
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+    RETURN left(null, 1)
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+    RETURN left(null, null)
+$$) AS (results agtype);
+SELECT * FROM l_substr(null, 1);
+SELECT * FROM l_substr(null, null);
+-- should fail
+SELECT * FROM cypher('expr', $$
+    RETURN left("123456789", null)
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+    RETURN left("123456789", -1)
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+    RETURN left()
+$$) AS (results agtype);
+SELECT * FROM l_substr('123456789', null);
+SELECT * FROM l_substr('123456789', -1);
+SELECT * FROM l_substr();
+--right()
+SELECT * FROM cypher('expr', $$
+    RETURN right("123456789", 1)
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+    RETURN right("123456789", 3)
+$$) AS (results agtype);
+-- should return null
+SELECT * FROM cypher('expr', $$
+    RETURN right("123456789", 0)
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+    RETURN right(null, 1)
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+    RETURN right(null, null)
+$$) AS (results agtype);
+SELECT * FROM r_substr(null, 1);
+SELECT * FROM r_substr(null, null);
+-- should fail
+SELECT * FROM cypher('expr', $$
+    RETURN right("123456789", null)
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+    RETURN right("123456789", -1)
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+    RETURN right()
+$$) AS (results agtype);
+SELECT * FROM r_substr('123456789', null);
+SELECT * FROM r_substr('123456789', -1);
+SELECT * FROM r_substr();
+-- substring()
+SELECT * FROM cypher('expr', $$
+    RETURN substring("0123456789", 0, 1)
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+    RETURN substring("0123456789", 1, 3)
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+    RETURN substring("0123456789", 3)
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+    RETURN substring("0123456789", 0)
+$$) AS (results agtype);
+SELECT * FROM b_substr('0123456789', 3, 2);
+SELECT * FROM b_substr('0123456789', 1);
+-- should return null
+SELECT * FROM cypher('expr', $$
+    RETURN substring(null, null, null)
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+    RETURN substring(null, null)
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+    RETURN substring(null, 1)
+$$) AS (results agtype);
+SELECT * FROM b_substr(null, null, null);
+SELECT * FROM b_substr(null, null);
+SELECT * FROM b_substr(null, 1);
+-- should fail
+SELECT * FROM cypher('expr', $$
+    RETURN substring("123456789", null)
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+    RETURN substring("123456789", 0, -1)
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+    RETURN substring("123456789", -1)
+$$) AS (results agtype);
+SELECT * FROM cypher('expr', $$
+    RETURN substring("123456789")
+$$) AS (results agtype);
+SELECT * FROM b_substr('123456789', null);
+SELECT * FROM b_substr('123456789', 0, -1);
+SELECT * FROM b_substr('123456789', -1);
+SELECT * FROM b_substr();
+
+--
 -- Cleanup
 --
 SELECT * FROM drop_graph('expr', true);
diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c
index 250a4b4..348f775 100644
--- a/src/backend/parser/cypher_expr.c
+++ b/src/backend/parser/cypher_expr.c
@@ -63,6 +63,9 @@
 #define FUNC_LTRIM      {"lTrim",      "l_trim",     ANYOID,    0, 0, 
AGTYPEOID, 1, 1, false}
 #define FUNC_RTRIM      {"rTrim",      "r_trim",     ANYOID,    0, 0, 
AGTYPEOID, 1, 1, false}
 #define FUNC_BTRIM      {"trim",       "b_trim",     ANYOID,    0, 0, 
AGTYPEOID, 1, 1, false}
+#define FUNC_RSUBSTR    {"right",      "r_substr",   ANYOID,    ANYOID, 0, 
AGTYPEOID, 2, 1, false}
+#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}
 
 /* supported functions */
 #define SUPPORTED_FUNCTIONS {FUNC_TYPE, FUNC_ENDNODE, FUNC_HEAD, FUNC_ID, \
@@ -71,7 +74,8 @@
                              FUNC_TOINTEGER, FUNC_TOBOOLEAN, FUNC_TOFLOAT, \
                              FUNC_EXISTS, FUNC_TOSTRING, FUNC_REVERSE, \
                              FUNC_TOUPPER, FUNC_TOLOWER, FUNC_LTRIM, \
-                             FUNC_RTRIM, FUNC_BTRIM}
+                             FUNC_RTRIM, FUNC_BTRIM, FUNC_RSUBSTR, \
+                             FUNC_LSUBSTR, FUNC_BSUBSTR}
 
 /* structure for supported function signatures */
 typedef struct function_signature
@@ -785,8 +789,13 @@ static Node *transform_cypher_function(cypher_parsestate 
*cpstate,
     if (func_operator_oid == InvalidOid)
         ereport(ERROR, (errmsg_internal("function \'%s\' not supported",
                                         cfunction->funcname)));
-    /* verify the number of passed arguments */
-    if (fs->nexprs != nexprs)
+    /*
+     * verify the number of passed arguments -
+     * if -1 its variable but at least 1
+     * otherwise they must match.
+     */
+    if (((fs->nexprs != -1) || (nexprs == 0)) &&
+        (fs->nexprs != nexprs))
         ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                         errmsg("invalid number of input parameters for %s()",
                                funcname)));
diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c
index af63a75..1a5fcd3 100644
--- a/src/backend/utils/adt/agtype.c
+++ b/src/backend/utils/adt/agtype.c
@@ -4902,3 +4902,454 @@ Datum b_trim(PG_FUNCTION_ARGS)
 
     PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result));
 }
+
+PG_FUNCTION_INFO_V1(r_substr);
+
+Datum r_substr(PG_FUNCTION_ARGS)
+{
+    int nargs;
+    Datum *args;
+    Datum arg;
+    bool *nulls;
+    Oid *types;
+    agtype_value agtv_result;
+    text *text_string = NULL;
+    char *string = NULL;
+    int string_len;
+    Oid type;
+
+    /* extract argument values */
+    nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls);
+
+    /* check number of args */
+    if (nargs != 2)
+        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                        errmsg("right() invalid number of arguments")));
+
+    /* check for a null string */
+    if (nargs < 0 || nulls[0])
+        PG_RETURN_NULL();
+
+    /* check for a null length */
+    if (nulls[1])
+        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                            errmsg("right() length parameter cannot be 
null")));
+
+    /* right() supports text, cstring, or the agtype string input */
+    arg = args[0];
+    type = types[0];
+
+    if (type != AGTYPEOID)
+    {
+        if (type == CSTRINGOID)
+            text_string = cstring_to_text(DatumGetCString(arg));
+        else if (type == TEXTOID)
+            text_string = DatumGetTextPP(arg);
+        else
+            ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                            errmsg("right() 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("right() 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)
+            text_string = cstring_to_text_with_len(agtv_value->val.string.val,
+                                                   agtv_value->val.string.len);
+        else
+            ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                            errmsg("right() unsuppoted argument agtype %d",
+                                   agtv_value->type)));
+    }
+
+    /* right() only supports integer and agtype integer for the second 
parameter. */
+    arg = args[1];
+    type = types[1];
+
+    if (type != AGTYPEOID)
+    {
+        if (type == INT2OID)
+            string_len = (int64) DatumGetInt16(arg);
+        else if (type == INT4OID)
+            string_len = (int64) DatumGetInt32(arg);
+        else if (type == INT8OID)
+            string_len = (int64) DatumGetInt64(arg);
+        else
+            ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                            errmsg("right() 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("right() only supports scalar arguments")));
+
+        agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0);
+
+        /* no need to check for agtype null because it is an error if found */
+        if (agtv_value->type != AGTV_INTEGER)
+            ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                            errmsg("right() unsuppoted argument agtype %d",
+                                   agtv_value->type)));
+
+        string_len = agtv_value->val.int_value;
+    }
+
+    /* negative values are not supported in the opencypher spec */
+    if (string_len < 0)
+        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                        errmsg("right() negative values are not supported for 
length")));
+
+    /*
+     * We need the string as a text string so that we can let PG deal with
+     * multibyte characters in the string.
+     */
+    text_string = DatumGetTextPP(DirectFunctionCall2(text_right,
+                                                     
PointerGetDatum(text_string),
+                                                     
Int64GetDatum(string_len)));
+
+    /* convert it back to a cstring */
+    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;
+    agtv_result.val.string.len = string_len;
+
+    PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result));
+}
+
+PG_FUNCTION_INFO_V1(l_substr);
+
+Datum l_substr(PG_FUNCTION_ARGS)
+{
+    int nargs;
+    Datum *args;
+    Datum arg;
+    bool *nulls;
+    Oid *types;
+    agtype_value agtv_result;
+    text *text_string = NULL;
+    char *string = NULL;
+    int string_len;
+    Oid type;
+
+    /* extract argument values */
+    nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls);
+
+    /* check number of args */
+    if (nargs != 2)
+        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                        errmsg("left() invalid number of arguments")));
+
+    /* check for a null string */
+    if (nargs < 0 || nulls[0])
+        PG_RETURN_NULL();
+
+    /* check for a null length */
+    if (nulls[1])
+        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                            errmsg("left() length parameter cannot be null")));
+
+    /* left() supports text, cstring, or the agtype string input */
+    arg = args[0];
+    type = types[0];
+
+    if (type != AGTYPEOID)
+    {
+        if (type == CSTRINGOID)
+            text_string = cstring_to_text(DatumGetCString(arg));
+        else if (type == TEXTOID)
+            text_string = DatumGetTextPP(arg);
+        else
+            ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                            errmsg("left() 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("left() 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)
+            text_string = cstring_to_text_with_len(agtv_value->val.string.val,
+                                                   agtv_value->val.string.len);
+        else
+            ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                            errmsg("left() unsuppoted argument agtype %d",
+                                   agtv_value->type)));
+    }
+
+    /* left() only supports integer and agtype integer for the second 
parameter. */
+    arg = args[1];
+    type = types[1];
+
+    if (type != AGTYPEOID)
+    {
+        if (type == INT2OID)
+            string_len = (int64) DatumGetInt16(arg);
+        else if (type == INT4OID)
+            string_len = (int64) DatumGetInt32(arg);
+        else if (type == INT8OID)
+            string_len = (int64) DatumGetInt64(arg);
+        else
+            ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                            errmsg("left() 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("left() only supports scalar arguments")));
+
+        agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0);
+
+        /* no need to check for agtype null because it is an error if found */
+        if (agtv_value->type != AGTV_INTEGER)
+            ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                            errmsg("left() unsuppoted argument agtype %d",
+                                   agtv_value->type)));
+
+        string_len = agtv_value->val.int_value;
+    }
+
+    /* negative values are not supported in the opencypher spec */
+    if (string_len < 0)
+        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                        errmsg("left() negative values are not supported for 
length")));
+
+    /*
+     * We need the string as a text string so that we can let PG deal with
+     * multibyte characters in the string.
+     */
+    text_string = DatumGetTextPP(DirectFunctionCall2(text_left,
+                                                     
PointerGetDatum(text_string),
+                                                     
Int64GetDatum(string_len)));
+
+    /* convert it back to a cstring */
+    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;
+    agtv_result.val.string.len = string_len;
+
+    PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result));
+}
+
+PG_FUNCTION_INFO_V1(b_substr);
+
+Datum b_substr(PG_FUNCTION_ARGS)
+{
+    int nargs;
+    Datum *args;
+    Datum arg;
+    bool *nulls;
+    Oid *types;
+    agtype_value agtv_result;
+    text *text_string = NULL;
+    char *string = NULL;
+    int param;
+    int string_start = 0;
+    int string_len = 0;
+    int i;
+    Oid type;
+
+    /* extract argument values */
+    nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls);
+
+    /* check number of args */
+    if (nargs < 2 || nargs > 3)
+        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                        errmsg("substring() invalid number of arguments")));
+
+    /* check for null */
+    if (nargs < 0 || nulls[0])
+        PG_RETURN_NULL();
+
+    /* neither offset or length can be null if there is a valid string */
+    if ((nargs == 2 && nulls[1]) ||
+        (nargs == 3 && nulls[2]))
+        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                            errmsg("substring() offset or length cannot be 
null")));
+
+    /* substring() supports text, cstring, or the agtype string input */
+    arg = args[0];
+    type = types[0];
+
+    if (type != AGTYPEOID)
+    {
+        if (type == CSTRINGOID)
+            text_string = cstring_to_text(DatumGetCString(arg));
+        else if (type == TEXTOID)
+            text_string = DatumGetTextPP(arg);
+        else
+            ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                            errmsg("substring() 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("substring() 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)
+            text_string = cstring_to_text_with_len(agtv_value->val.string.val,
+                                                   agtv_value->val.string.len);
+        else
+            ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                            errmsg("substring() unsuppoted argument agtype %d",
+                                   agtv_value->type)));
+    }
+
+    /*
+     * substring() only supports integer and agtype integer for the second and
+     * third parameters values.
+     */
+    for (i = 1; i < nargs; i++)
+    {
+        arg = args[i];
+        type = types[i];
+
+        if (type != AGTYPEOID)
+        {
+            if (type == INT2OID)
+                param = (int64) DatumGetInt16(arg);
+            else if (type == INT4OID)
+                param = (int64) DatumGetInt32(arg);
+            else if (type == INT8OID)
+                param = (int64) DatumGetInt64(arg);
+            else
+                ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                errmsg("substring() 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("substring() only supports scalar 
arguments")));
+
+            agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 
0);
+
+            /* no need to check for agtype null because it is an error if 
found */
+            if (agtv_value->type != AGTV_INTEGER)
+                ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                errmsg("substring() unsuppoted argument agtype 
%d",
+                                       agtv_value->type)));
+
+            param = agtv_value->val.int_value;
+        }
+
+        if (i == 1)
+            string_start = param;
+        if (i == 2)
+            string_len = param;
+    }
+
+    /* negative values are not supported in the opencypher spec */
+    if (string_start < 0 || string_len < 0)
+        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                        errmsg("substring() negative values are not supported 
for offset or length")));
+
+    /* cypher substring is 0 based while PG's is 1 based */
+    string_start += 1;
+
+    /*
+     * We need the string as a text string so that we can let PG deal with
+     * multibyte characters in the string.
+     */
+
+    /* if optional length is left out */
+    if (nargs == 2)
+         text_string = DatumGetTextPP(DirectFunctionCall2(text_substr_no_len,
+                                                          
PointerGetDatum(text_string),
+                                                          
Int64GetDatum(string_start)));
+    /* if length is given */
+    else
+        text_string = DatumGetTextPP(DirectFunctionCall3(text_substr,
+                                                         
PointerGetDatum(text_string),
+                                                         
Int64GetDatum(string_start),
+                                                         
Int64GetDatum(string_len)));
+
+    /* convert it back to a cstring */
+    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;
+    agtv_result.val.string.len = string_len;
+
+    PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result));
+}

Reply via email to