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));
+}