From f41f6da6d44a57b60d9a9ade6cef840608159948 Mon Sep 17 00:00:00 2001 From: Marcos Silva <94569840+M4rcxs@users.noreply.github.com> Date: Mon, 17 Jul 2023 20:40:59 -0300 Subject: [PATCH] added toIntegerList() function (#1025) - This function is inspired by toIntegerList() in OpenCypher. - The function toIntegerList() takes a list of values and produces a new list containing only the integer values. If any values cannot be converted to an integer, they will be represented as null in the returned list. - The result of the toIntegerList() function is a list that contains the converted elements. The converted values in the list can be either integer values or null, depending on the input value. - Also added the regression tests. --- age--1.3.0.sql | 8 +++ regress/expected/expr.out | 69 +++++++++++++++++++ regress/sql/expr.sql | 30 +++++++++ src/backend/utils/adt/agtype.c | 118 +++++++++++++++++++++++++++++++++ 4 files changed, 225 insertions(+) diff --git a/age--1.3.0.sql b/age--1.3.0.sql index efd14a5fa..8bfa9a93e 100644 --- a/age--1.3.0.sql +++ b/age--1.3.0.sql @@ -3530,6 +3530,14 @@ RETURNS NULL ON NULL INPUT PARALLEL SAFE AS 'MODULE_PATHNAME'; +CREATE FUNCTION ag_catalog.age_tointegerlist(variadic "any") +RETURNS agtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + CREATE FUNCTION ag_catalog.age_tostring(variadic "any") RETURNS agtype LANGUAGE c diff --git a/regress/expected/expr.out b/regress/expected/expr.out index fae09deb6..b01cf977b 100644 --- a/regress/expected/expr.out +++ b/regress/expected/expr.out @@ -3032,6 +3032,75 @@ ERROR: function ag_catalog.age_tointeger() does not exist LINE 2: RETURN toInteger() ^ HINT: No function matches the given name and argument types. You might need to add explicit type casts. +-- toIntegerList() +SELECT * FROM cypher('expr', $$ + RETURN toIntegerList([1, 7.8, 9.0, '88']) +$$) AS (toIntegerList agtype); + tointegerlist +--------------- + [1, 7, 9, 88] +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN toIntegerList([4.2, '123', '8', 8]) +$$) AS (toIntegerList agtype); + tointegerlist +---------------- + [4, 123, 8, 8] +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN toIntegerList(['41', '12', 2]) +$$) AS (toIntegerList agtype); + tointegerlist +--------------- + [41, 12, 2] +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN toIntegerList([1, 2, 3, '10.2']) +$$) AS (toIntegerList agtype); + tointegerlist +--------------- + [1, 2, 3, 10] +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN toIntegerList([0000]) +$$) AS (toIntegerList agtype); + tointegerlist +--------------- + [0] +(1 row) + +-- should return null +SELECT * FROM cypher('expr', $$ + RETURN toIntegerList(["false_", 'asdsad', '123k1kdk1']) +$$) AS (toIntegerList agtype); + tointegerlist +-------------------- + [null, null, null] +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN toIntegerList([null, '123false', 'one']) +$$) AS (toIntegerList agtype); + tointegerlist +-------------------- + [null, null, null] +(1 row) + +-- should fail +SELECT * FROM cypher('expr', $$ + RETURN toIntegerList(123, '123') +$$) AS (toIntegerList agtype); +ERROR: toIntegerList() argument must resolve to a list or null +SELECT * FROM cypher('expr', $$ + RETURN toIntegerList(32[]) +$$) AS (toIntegerList agtype); +ERROR: syntax error at or near "]" +LINE 2: RETURN toIntegerList(32[]) + ^ -- length() of a path SELECT * FROM cypher('expr', $$ RETURN length([{id: 0, label: "vertex 0", properties: {}}::vertex, {id: 2, label: "edge 0", end_id: 1, start_id: 0, properties: {}}::edge, {id: 1, label: "vertex 1", properties: {}}::vertex]::path) diff --git a/regress/sql/expr.sql b/regress/sql/expr.sql index 7c6f913f7..d4a7efbda 100644 --- a/regress/sql/expr.sql +++ b/regress/sql/expr.sql @@ -1362,6 +1362,36 @@ $$) AS (toInteger agtype); SELECT * FROM cypher('expr', $$ RETURN toInteger() $$) AS (toInteger agtype); +-- toIntegerList() +SELECT * FROM cypher('expr', $$ + RETURN toIntegerList([1, 7.8, 9.0, '88']) +$$) AS (toIntegerList agtype); +SELECT * FROM cypher('expr', $$ + RETURN toIntegerList([4.2, '123', '8', 8]) +$$) AS (toIntegerList agtype); +SELECT * FROM cypher('expr', $$ + RETURN toIntegerList(['41', '12', 2]) +$$) AS (toIntegerList agtype); +SELECT * FROM cypher('expr', $$ + RETURN toIntegerList([1, 2, 3, '10.2']) +$$) AS (toIntegerList agtype); +SELECT * FROM cypher('expr', $$ + RETURN toIntegerList([0000]) +$$) AS (toIntegerList agtype); +-- should return null +SELECT * FROM cypher('expr', $$ + RETURN toIntegerList(["false_", 'asdsad', '123k1kdk1']) +$$) AS (toIntegerList agtype); +SELECT * FROM cypher('expr', $$ + RETURN toIntegerList([null, '123false', 'one']) +$$) AS (toIntegerList agtype); +-- should fail +SELECT * FROM cypher('expr', $$ + RETURN toIntegerList(123, '123') +$$) AS (toIntegerList agtype); +SELECT * FROM cypher('expr', $$ + RETURN toIntegerList(32[]) +$$) AS (toIntegerList agtype); -- length() of a path SELECT * FROM cypher('expr', $$ RETURN length([{id: 0, label: "vertex 0", properties: {}}::vertex, {id: 2, label: "edge 0", end_id: 1, start_id: 0, properties: {}}::edge, {id: 1, label: "vertex 1", properties: {}}::vertex]::path) diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index 00c7b2d4e..376137aba 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -5603,6 +5603,124 @@ Datum age_tointeger(PG_FUNCTION_ARGS) PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); } +PG_FUNCTION_INFO_V1(age_tointegerlist); +/* + * toIntegerList() converts a list of values and returns a list of integers point values. + * If any values are not convertible to integer they will be null in the list returned. + */ +Datum age_tointegerlist(PG_FUNCTION_ARGS) +{ + agtype *agt_arg = NULL; + agtype_in_state agis_result; + agtype_value *elem; + agtype_value integer_elem; + int count; + int i; + char *string = NULL; + int integer_num; + float float_num; + int is_float; + + /* check for null */ + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + agt_arg = AG_GET_ARG_AGTYPE_P(0); + /* check for an array */ + if (!AGT_ROOT_IS_ARRAY(agt_arg) || AGT_ROOT_IS_SCALAR(agt_arg)) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("toIntegerList() argument must resolve to a list or null"))); + + count = AGT_ROOT_COUNT(agt_arg); + + /* if we have an empty list or only one element in the list, return null */ + if (count == 0) + PG_RETURN_NULL(); + + /* clear the result structure */ + MemSet(&agis_result, 0, sizeof(agtype_in_state)); + + /* push the beginning of the array */ + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_BEGIN_ARRAY, NULL); + + /* iterate through the list */ + for (i = 0; i < count; i++) + { + // TODO: check element's type, it's value, and convert it to integer if possible. + elem = get_ith_agtype_value_from_container(&agt_arg->root, i); + integer_elem.type = AGTV_INTEGER; + + switch (elem->type) + { + case AGTV_STRING: + + string = elem->val.string.val; + integer_elem.type = AGTV_INTEGER; + integer_elem.val.int_value = atoi(string); + + if (*string == '+' || *string == '-' || (*string >= '0' && *string <= '9')) + { + is_float = 1; + while (*(++string)) + { + + if(!(*string >= '0' && *string <= '9')) + { + if(*string == '.' && is_float) + { + is_float--; + } + else + { + integer_elem.type = AGTV_NULL; + break; + } + } + } + } + else + { + + integer_elem.type = AGTV_NULL; + } + + agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &integer_elem); + + break; + + case AGTV_FLOAT: + + integer_elem.type = AGTV_INTEGER; + float_num = elem->val.float_value; + integer_elem.val.int_value = (int)float_num; + agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &integer_elem); + + break; + + case AGTV_INTEGER: + + integer_elem.type = AGTV_INTEGER; + integer_num = elem->val.int_value; + integer_elem.val.int_value = integer_num; + agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &integer_elem); + + break; + + default: + + integer_elem.type = AGTV_NULL; + agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &integer_elem); + + break; + } + } + agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_END_ARRAY, NULL); + + PG_RETURN_POINTER(agtype_value_to_agtype(agis_result.res)); +} + PG_FUNCTION_INFO_V1(age_size); Datum age_size(PG_FUNCTION_ARGS)