From 8673e23f970d314ec6690177b5c9f2737e74efbe Mon Sep 17 00:00:00 2001 From: Matheus Date: Fri, 18 Aug 2023 17:47:55 -0300 Subject: [PATCH] Added toBooleanList() - Same as openCypher's toBooleanList() function: https://neo4j.com/docs/cypher-manual/current/functions/list/#functions-tobooleanlist - The toBooleanList() function converts a list of values and returns a list of boolean values. - If any values are not convertible to boolean they will be null in the list returned. - Created regression tests for it. --- age--1.3.0.sql | 8 ++++ regress/expected/expr.out | 81 ++++++++++++++++++++++++++++++++ regress/sql/expr.sql | 48 +++++++++++++++++++ src/backend/utils/adt/agtype.c | 85 ++++++++++++++++++++++++++++++++++ 4 files changed, 222 insertions(+) diff --git a/age--1.3.0.sql b/age--1.3.0.sql index 90097a7ef..3a9116272 100644 --- a/age--1.3.0.sql +++ b/age--1.3.0.sql @@ -3507,6 +3507,14 @@ RETURNS NULL ON NULL INPUT PARALLEL SAFE AS 'MODULE_PATHNAME'; +CREATE FUNCTION ag_catalog.age_tobooleanlist(variadic "any") +RETURNS agtype +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + CREATE FUNCTION ag_catalog.age_tofloat(variadic "any") RETURNS agtype LANGUAGE c diff --git a/regress/expected/expr.out b/regress/expected/expr.out index 1cdfaddb0..3c0e487bb 100644 --- a/regress/expected/expr.out +++ b/regress/expected/expr.out @@ -2827,6 +2827,87 @@ ERROR: function ag_catalog.age_toboolean() does not exist LINE 2: RETURN toBoolean() ^ HINT: No function matches the given name and argument types. You might need to add explicit type casts. +-- toBooleanList() +SELECT * FROM cypher('expr', $$ + RETURN toBooleanList([true, false, true]) +$$) AS (toBooleanList agtype); + tobooleanlist +--------------------- + [true, false, true] +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN toBooleanList(["true", "false", "true"]) +$$) AS (toBooleanList agtype); + tobooleanlist +--------------------- + [true, false, true] +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN toBooleanList(["True", "False", "True"]) +$$) AS (toBooleanList agtype); + tobooleanlist +--------------------- + [true, false, true] +(1 row) + +-- should return null +SELECT * FROM cypher('expr', $$ + RETURN toBooleanList([]) +$$) AS (toBooleanList agtype); + tobooleanlist +--------------- + +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN toBooleanList([null, null, null]) +$$) AS (toBooleanList agtype); + tobooleanlist +-------------------- + [null, null, null] +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN toBooleanList(["Hello", "world!"]) +$$) AS (toBooleanList agtype); + tobooleanlist +--------------- + [null, null] +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN toBooleanList([["A", "B"], ["C", "D"]]) +$$) AS (toBooleanList agtype); + tobooleanlist +--------------- + [null, null] +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN toBooleanList([0,1,2,3,4]) +$$) AS (toBooleanList agtype); + tobooleanlist +-------------------------------- + [null, null, null, null, null] +(1 row) + +-- should fail +SELECT * FROM cypher('expr', $$ + RETURN toBooleanList(fail) +$$) AS (toBooleanList agtype); +ERROR: could not find rte for fail +LINE 2: RETURN toBooleanList(fail) + ^ +SELECT * FROM cypher('expr', $$ + RETURN toBooleanList("fail") +$$) AS (toBooleanList agtype); +ERROR: toBooleanList() argument must resolve to a list or null +SELECT * FROM cypher('expr', $$ + RETURN toBooleanList(123) +$$) AS (toBooleanList agtype); +ERROR: toBooleanList() argument must resolve to a list or null -- toFloat() SELECT * FROM cypher('expr', $$ RETURN toFloat(1) diff --git a/regress/sql/expr.sql b/regress/sql/expr.sql index 82421d9dd..4788fe254 100644 --- a/regress/sql/expr.sql +++ b/regress/sql/expr.sql @@ -1269,6 +1269,54 @@ $$) AS (toBoolean agtype); SELECT * FROM cypher('expr', $$ RETURN toBoolean() $$) AS (toBoolean agtype); + +-- toBooleanList() +SELECT * FROM cypher('expr', $$ + RETURN toBooleanList([true, false, true]) +$$) AS (toBooleanList agtype); + +SELECT * FROM cypher('expr', $$ + RETURN toBooleanList(["true", "false", "true"]) +$$) AS (toBooleanList agtype); + +SELECT * FROM cypher('expr', $$ + RETURN toBooleanList(["True", "False", "True"]) +$$) AS (toBooleanList agtype); + +-- should return null +SELECT * FROM cypher('expr', $$ + RETURN toBooleanList([]) +$$) AS (toBooleanList agtype); + +SELECT * FROM cypher('expr', $$ + RETURN toBooleanList([null, null, null]) +$$) AS (toBooleanList agtype); + +SELECT * FROM cypher('expr', $$ + RETURN toBooleanList(["Hello", "world!"]) +$$) AS (toBooleanList agtype); + +SELECT * FROM cypher('expr', $$ + RETURN toBooleanList([["A", "B"], ["C", "D"]]) +$$) AS (toBooleanList agtype); + +SELECT * FROM cypher('expr', $$ + RETURN toBooleanList([0,1,2,3,4]) +$$) AS (toBooleanList agtype); + +-- should fail +SELECT * FROM cypher('expr', $$ + RETURN toBooleanList(fail) +$$) AS (toBooleanList agtype); + +SELECT * FROM cypher('expr', $$ + RETURN toBooleanList("fail") +$$) AS (toBooleanList agtype); + +SELECT * FROM cypher('expr', $$ + RETURN toBooleanList(123) +$$) AS (toBooleanList agtype); + -- toFloat() SELECT * FROM cypher('expr', $$ RETURN toFloat(1) diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index 815a731a7..40ac4140c 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -5200,6 +5200,91 @@ Datum age_toboolean(PG_FUNCTION_ARGS) PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); } +PG_FUNCTION_INFO_V1(age_tobooleanlist); +/* + * Converts a list of values and returns a list of boolean values. If any values are not convertible + * to boolean, they will be null in the list returned. + * */ +Datum age_tobooleanlist(PG_FUNCTION_ARGS) +{ + agtype *agt_arg = NULL; + agtype_in_state agis_result; + agtype_value *elem; + agtype_value bool_elem; + char *string = NULL; + int count; + int i; + + // 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("toBooleanList() 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++) + { + elem = get_ith_agtype_value_from_container(&agt_arg->root, i); + bool_elem.type = AGTV_BOOL; + + switch (elem->type) + { + case AGTV_STRING: + string = elem->val.string.val; + if (pg_strcasecmp(string, "true") == 0) + { + bool_elem.val.boolean = true; + agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &bool_elem); + } + else if (pg_strcasecmp(string, "false") == 0) + { + bool_elem.val.boolean = false; + agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &bool_elem); + } + else + { + bool_elem.type = AGTV_NULL; + agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &bool_elem); + } + break; + + + case AGTV_BOOL: + bool_elem.val.boolean = elem->val.boolean; + agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &bool_elem); + break; + + + default: + bool_elem.type = AGTV_NULL; + agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &bool_elem); + break; + } + } + + // push the end of the array + 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_tofloat); Datum age_tofloat(PG_FUNCTION_ARGS)