Skip to content

Commit

Permalink
Added the toStringList() function (#1084)
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
M4rcxs authored Aug 8, 2023
1 parent 20fcc1d commit 4274f10
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 0 deletions.
8 changes: 8 additions & 0 deletions age--1.3.0.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
63 changes: 63 additions & 0 deletions regress/expected/expr.out
Original file line number Diff line number Diff line change
Expand Up @@ -3284,6 +3284,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)
--
Expand Down
27 changes: 27 additions & 0 deletions regress/sql/expr.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
100 changes: 100 additions & 0 deletions src/backend/utils/adt/agtype.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "postgres.h"

#include <math.h>
#include <float.h>

#include "access/genam.h"
#include "access/heapam.h"
Expand Down Expand Up @@ -6196,6 +6197,105 @@ 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;
float float_num;
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:

float_num = elem->val.float_value;
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, "%d", 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)
{
Expand Down

0 comments on commit 4274f10

Please sign in to comment.