From 9ab88ec76057a53de6393f1d8dbec7f8d2a16a83 Mon Sep 17 00:00:00 2001 From: Marcos Silva Date: Mon, 24 Jul 2023 20:32:14 -0300 Subject: [PATCH] Added the toStringList() function - This function is inspired by the "tostringlist" function in OpenCypher.https://neo4j.com/docs/cypher-manual/current/functions/list/#functions-tostringlist - toStringList() converts a list of values and returns a list of string values. If any values are not convertible to string they will be null in the list returned. - A list containing the converted elements; depending on the input value a converted value is either a string value or null. - Also added the regression tests --- age--1.3.0.sql | 8 +++ regress/expected/expr.out | 63 ++++++++++++++++++++++ regress/sql/expr.sql | 27 ++++++++++ src/backend/utils/adt/agtype.c | 98 ++++++++++++++++++++++++++++++++++ 4 files changed, 196 insertions(+) diff --git a/age--1.3.0.sql b/age--1.3.0.sql index 8bfa9a93e..2b0ddef52 100644 --- a/age--1.3.0.sql +++ b/age--1.3.0.sql @@ -3546,6 +3546,14 @@ RETURNS NULL ON NULL INPUT PARALLEL SAFE AS 'MODULE_PATHNAME'; +CREATE FUNCTION ag_catalog.age_tostringlist(variadic "any") +RETURNS agtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + CREATE FUNCTION ag_catalog.age_size(variadic "any") RETURNS agtype LANGUAGE c diff --git a/regress/expected/expr.out b/regress/expected/expr.out index b01cf977b..52f5eb026 100644 --- a/regress/expected/expr.out +++ b/regress/expected/expr.out @@ -3276,6 +3276,69 @@ ERROR: function ag_catalog.age_tostring() does not exist LINE 1: SELECT * FROM cypher('expr', $$ RETURN toString() $$) AS (re... ^ HINT: No function matches the given name and argument types. You might need to add explicit type casts. +-- toStringList() -- +SELECT * FROM cypher('expr', $$ + RETURN toStringList([5, 10, 7.8, 9, 1.3]) +$$) AS (toStringList agtype); + tostringlist +-------------------------------- + ["5", "10", "7.8", "9", "1.3"] +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN toStringList(['test', 89, 'again', 7.1, 9]) +$$) AS (toStringList agtype); + tostringlist +------------------------------------- + ["test", "89", "again", "7.1", "9"] +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN toStringList([null, false, true, 'string']) +$$) AS (toStringList agtype); + tostringlist +------------------------------ + [null, null, null, "string"] +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN toStringList([9.123456789, 5.123, 1.12345, 0.123123]) +$$) AS (toStringList agtype); + tostringlist +------------------------------------------------- + ["9.123456789", "5.123", "1.12345", "0.123123"] +(1 row) + +-- should return null +SELECT * FROM cypher('expr', $$ + RETURN toStringList([null]) +$$) AS (toStringList agtype); + tostringlist +-------------- + [null] +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN toStringList([true, false, true, true]) +$$) AS (toStringList agtype); + tostringlist +-------------------------- + [null, null, null, null] +(1 row) + +-- should fail +SELECT * FROM cypher('expr', $$ + RETURN toStringList([['a', b]]) +$$) AS (toStringList agtype); +ERROR: could not find rte for b +LINE 2: RETURN toStringList([['a', b]]) + ^ +SELECT * FROM cypher('expr', $$ + RETURN toStringList([test]) +$$) AS (toStringList agtype); +ERROR: could not find rte for test +LINE 2: RETURN toStringList([test]) + ^ -- -- reverse(string) -- diff --git a/regress/sql/expr.sql b/regress/sql/expr.sql index d4a7efbda..6894172cb 100644 --- a/regress/sql/expr.sql +++ b/regress/sql/expr.sql @@ -1441,6 +1441,33 @@ SELECT * FROM age_toString(agtype_in(null)); -- should fail SELECT * FROM age_toString(); SELECT * FROM cypher('expr', $$ RETURN toString() $$) AS (results agtype); +-- toStringList() -- +SELECT * FROM cypher('expr', $$ + RETURN toStringList([5, 10, 7.8, 9, 1.3]) +$$) AS (toStringList agtype); +SELECT * FROM cypher('expr', $$ + RETURN toStringList(['test', 89, 'again', 7.1, 9]) +$$) AS (toStringList agtype); +SELECT * FROM cypher('expr', $$ + RETURN toStringList([null, false, true, 'string']) +$$) AS (toStringList agtype); +SELECT * FROM cypher('expr', $$ + RETURN toStringList([9.123456789, 5.123, 1.12345, 0.123123]) +$$) AS (toStringList agtype); +-- should return null +SELECT * FROM cypher('expr', $$ + RETURN toStringList([null]) +$$) AS (toStringList agtype); +SELECT * FROM cypher('expr', $$ + RETURN toStringList([true, false, true, true]) +$$) AS (toStringList agtype); +-- should fail +SELECT * FROM cypher('expr', $$ + RETURN toStringList([['a', b]]) +$$) AS (toStringList agtype); +SELECT * FROM cypher('expr', $$ + RETURN toStringList([test]) +$$) AS (toStringList agtype); -- -- reverse(string) diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index 376137aba..3fd7a93ac 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -31,6 +31,7 @@ #include "postgres.h" #include +#include #include "access/genam.h" #include "access/heapam.h" @@ -6149,6 +6150,103 @@ Datum age_tostring(PG_FUNCTION_ARGS) PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); } +PG_FUNCTION_INFO_V1(age_tostringlist); +/* + * toStringList() converts a list of values and returns a list of String values. + * If any values are not convertible to string point they will be null in the list returned. + */ +Datum age_tostringlist(PG_FUNCTION_ARGS) +{ + agtype *agt_arg = NULL; + agtype_in_state agis_result; + agtype_value *elem; + agtype_value string_elem; + int count; + int i; + char buffer[64]; + + /* 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("toStringList() 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 string if possible. + elem = get_ith_agtype_value_from_container(&agt_arg->root, i); + string_elem.type = AGTV_STRING; + + switch (elem->type) + { + case AGTV_STRING: + + if(!elem) + { + string_elem.type = AGTV_NULL; + + agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &string_elem); + } + + string_elem.val.string.val = elem->val.string.val; + string_elem.val.string.len = elem->val.string.len; + + agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &string_elem); + + break; + + case AGTV_FLOAT: + + sprintf(buffer, "%.*g", DBL_DIG, elem->val.float_value); + string_elem.val.string.val = pstrdup(buffer); + string_elem.val.string.len = strlen(buffer); + + agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &string_elem); + + break; + + case AGTV_INTEGER: + + sprintf(buffer, "%ld", elem->val.int_value); + string_elem.val.string.val = pstrdup(buffer); + string_elem.val.string.len = strlen(buffer); + + agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &string_elem); + + break; + + default: + + string_elem.type = AGTV_NULL; + agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &string_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)); +} + static agtype_iterator *get_next_list_element(agtype_iterator *it, agtype_container *agtc, agtype_value *elem) {