This is an automated email from the ASF dual-hosted git repository. jgemignani pushed a commit to branch PG12 in repository https://gitbox.apache.org/repos/asf/age.git
commit fb9547246faf4c9d1f2230af94d3b29affb3aa52 Author: Muhammad Taha Naveed <[email protected]> AuthorDate: Tue Feb 28 01:23:31 2023 +0500 Implement isEmpty() predicate function (#710) - Added isEmpty() predicate function to check if a List, map or string is empty. - Added regression tests for isEmpty() function. Co-authored-by: John Gemignani <[email protected]> --- age--1.1.1.sql | 8 ++ regress/expected/catalog.out | 2 +- regress/expected/cypher_match.out | 160 ++++++++++++++++++++++++++++++++++++++ regress/sql/cypher_match.sql | 97 +++++++++++++++++++++++ src/backend/utils/adt/agtype.c | 81 +++++++++++++++++++ 5 files changed, 347 insertions(+), 1 deletion(-) diff --git a/age--1.1.1.sql b/age--1.1.1.sql index e9aceffe..304d27db 100644 --- a/age--1.1.1.sql +++ b/age--1.1.1.sql @@ -3555,6 +3555,14 @@ STABLE PARALLEL SAFE AS 'MODULE_PATHNAME'; +CREATE FUNCTION ag_catalog.age_isempty(agtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + CREATE FUNCTION ag_catalog.age_label(agtype) RETURNS agtype LANGUAGE c diff --git a/regress/expected/catalog.out b/regress/expected/catalog.out index 1e62cf9e..c7d5cc3a 100644 --- a/regress/expected/catalog.out +++ b/regress/expected/catalog.out @@ -31,7 +31,7 @@ NOTICE: graph "graph" has been created SELECT * FROM ag_graph WHERE name = 'graph'; graphid | name | namespace ---------+-------+----------- - 17593 | graph | graph + 17595 | graph | graph (1 row) -- create a label to test drop_label() diff --git a/regress/expected/cypher_match.out b/regress/expected/cypher_match.out index 6ff9a3c6..9ec4112d 100644 --- a/regress/expected/cypher_match.out +++ b/regress/expected/cypher_match.out @@ -816,6 +816,166 @@ AS (u agtype); ERROR: syntax error at or near ")" LINE 2: $$MATCH (u) WHERE EXISTS(u) RETURN u$$) ^ +-- +-- MATCH with WHERE isEmpty(property) +-- +SELECT create_graph('for_isEmpty'); +NOTICE: graph "for_isEmpty" has been created + create_graph +-------------- + +(1 row) + +-- Create vertices +SELECT * FROM cypher('for_isEmpty', + $$CREATE (u:for_pred {id:1, type: "empty", list: [], map: {}, string: ""}), + (v:for_pred {id:2, type: "filled", list: [1], map: {a:1}, string: "a"}), + (w:for_pred)$$) +AS (a agtype); + a +--- +(0 rows) + +-- Match vertices with empty properties +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE isEmpty(u.list) RETURN properties(u) $$) +AS (u agtype); + u +----------------------------------------------------------------- + {"id": 1, "map": {}, "list": [], "type": "empty", "string": ""} +(1 row) + +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE isEmpty(u.map) RETURN properties(u) $$) +AS (u agtype); + u +----------------------------------------------------------------- + {"id": 1, "map": {}, "list": [], "type": "empty", "string": ""} +(1 row) + +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE isEmpty(u.string) RETURN properties(u) $$) +AS (u agtype); + u +----------------------------------------------------------------- + {"id": 1, "map": {}, "list": [], "type": "empty", "string": ""} +(1 row) + +-- Match vertices with non-empty properties +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE NOT isEmpty(u.list) RETURN properties(u) $$) +AS (u agtype); + u +-------------------------------------------------------------------------- + {"id": 2, "map": {"a": 1}, "list": [1], "type": "filled", "string": "a"} +(1 row) + +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE NOT isEmpty(u.map) RETURN properties(u) $$) +AS (u agtype); + u +-------------------------------------------------------------------------- + {"id": 2, "map": {"a": 1}, "list": [1], "type": "filled", "string": "a"} +(1 row) + +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE NOT isEmpty(u.string) RETURN properties(u) $$) +AS (u agtype); + u +-------------------------------------------------------------------------- + {"id": 2, "map": {"a": 1}, "list": [1], "type": "filled", "string": "a"} +(1 row) + +-- Match vertices with no properties +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE isEmpty(properties(u)) RETURN properties(u) $$) +AS (u agtype); + u +---- + {} +(1 row) + +-- Match vertices with properties +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE NOT isEmpty(properties(u)) RETURN properties(u) $$) +AS (u agtype); + u +-------------------------------------------------------------------------- + {"id": 1, "map": {}, "list": [], "type": "empty", "string": ""} + {"id": 2, "map": {"a": 1}, "list": [1], "type": "filled", "string": "a"} +(2 rows) + +-- Match vertices with null property (should return nothing since WHERE null) +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE isEmpty(u.tree) RETURN properties(u) $$) +AS (u agtype); + u +--- +(0 rows) + +-- Match and Return bool +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE isEmpty(u.list) RETURN isEmpty(u.list), u.type $$) +AS (b agtype, type agtype); + b | type +------+--------- + true | "empty" +(1 row) + +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE NOT isEmpty(u.list) RETURN isEmpty(u.list), u.type $$) +AS (b agtype, type agtype); + b | type +-------+---------- + false | "filled" +(1 row) + +-- Return null on null +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) RETURN isEmpty(u.tree) $$) +AS (b agtype); + b +--- + + + +(3 rows) + +-- Should give an error +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE isEmpty(u) RETURN properties(u) $$) +AS (u agtype); +ERROR: isEmpty() unsupported argument, expected a List, Map, or String +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE isEmpty(1) RETURN properties(u) $$) +AS (u agtype); +ERROR: isEmpty() unsupported argument, expected a List, Map, or String +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE isEmpty(1,2,3) RETURN properties(u) $$) +AS (u agtype); +ERROR: function ag_catalog.age_isempty(agtype, agtype, agtype) does not exist +LINE 2: $$MATCH (u:for_pred) WHERE isEmpty(1,2,3) RETURN properties... + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE isEmpty() RETURN properties(u) $$) +AS (u agtype); +ERROR: function ag_catalog.age_isempty() does not exist +LINE 2: $$MATCH (u:for_pred) WHERE isEmpty() RETURN properties(u) $... + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +-- clean up +SELECT drop_graph('for_isEmpty', true); +NOTICE: drop cascades to 3 other objects +DETAIL: drop cascades to table "for_isEmpty"._ag_label_vertex +drop cascades to table "for_isEmpty"._ag_label_edge +drop cascades to table "for_isEmpty".for_pred +NOTICE: graph "for_isEmpty" has been dropped + drop_graph +------------ + +(1 row) + -- --Distinct -- diff --git a/regress/sql/cypher_match.sql b/regress/sql/cypher_match.sql index c4c48bf7..aeed9f38 100644 --- a/regress/sql/cypher_match.sql +++ b/regress/sql/cypher_match.sql @@ -415,6 +415,103 @@ SELECT * FROM cypher('cypher_match', $$MATCH (u) WHERE EXISTS(u) RETURN u$$) AS (u agtype); +-- +-- MATCH with WHERE isEmpty(property) +-- + +SELECT create_graph('for_isEmpty'); + +-- Create vertices + +SELECT * FROM cypher('for_isEmpty', + $$CREATE (u:for_pred {id:1, type: "empty", list: [], map: {}, string: ""}), + (v:for_pred {id:2, type: "filled", list: [1], map: {a:1}, string: "a"}), + (w:for_pred)$$) +AS (a agtype); + +-- Match vertices with empty properties + +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE isEmpty(u.list) RETURN properties(u) $$) +AS (u agtype); + +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE isEmpty(u.map) RETURN properties(u) $$) +AS (u agtype); + +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE isEmpty(u.string) RETURN properties(u) $$) +AS (u agtype); + +-- Match vertices with non-empty properties + +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE NOT isEmpty(u.list) RETURN properties(u) $$) +AS (u agtype); + +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE NOT isEmpty(u.map) RETURN properties(u) $$) +AS (u agtype); + +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE NOT isEmpty(u.string) RETURN properties(u) $$) +AS (u agtype); + +-- Match vertices with no properties + +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE isEmpty(properties(u)) RETURN properties(u) $$) +AS (u agtype); + +-- Match vertices with properties + +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE NOT isEmpty(properties(u)) RETURN properties(u) $$) +AS (u agtype); + +-- Match vertices with null property (should return nothing since WHERE null) + +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE isEmpty(u.tree) RETURN properties(u) $$) +AS (u agtype); + +-- Match and Return bool + +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE isEmpty(u.list) RETURN isEmpty(u.list), u.type $$) +AS (b agtype, type agtype); + +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE NOT isEmpty(u.list) RETURN isEmpty(u.list), u.type $$) +AS (b agtype, type agtype); + +-- Return null on null + +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) RETURN isEmpty(u.tree) $$) +AS (b agtype); + +-- Should give an error + +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE isEmpty(u) RETURN properties(u) $$) +AS (u agtype); + +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE isEmpty(1) RETURN properties(u) $$) +AS (u agtype); + +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE isEmpty(1,2,3) RETURN properties(u) $$) +AS (u agtype); + +SELECT * FROM cypher('for_isEmpty', + $$MATCH (u:for_pred) WHERE isEmpty() RETURN properties(u) $$) +AS (u agtype); + +-- clean up +SELECT drop_graph('for_isEmpty', true); + -- --Distinct -- diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index 7c745faa..4aa724c2 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -5516,6 +5516,87 @@ Datum age_exists(PG_FUNCTION_ARGS) PG_RETURN_BOOL(true); } +PG_FUNCTION_INFO_V1(age_isempty); +/* + * Executor function for isEmpty(property). + */ + +Datum age_isempty(PG_FUNCTION_ARGS) +{ + Datum *args; + Datum arg; + bool *nulls; + Oid *types; + char *string = NULL; + Oid type; + int64 result; + + /* extract argument values */ + extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + + /* + * isEmpty() supports cstring, text, or the agtype string or list input + */ + arg = args[0]; + type = types[0]; + + if (type == CSTRINGOID) + { + string = DatumGetCString(arg); + result = strlen(string); + } + else if (type == TEXTOID) + { + string = text_to_cstring(DatumGetTextPP(arg)); + result = strlen(string); + } + else if (type == AGTYPEOID) + { + agtype *agt_arg; + + /* get the agtype argument */ + agt_arg = DATUM_GET_AGTYPE_P(arg); + + if (AGT_ROOT_IS_SCALAR(agt_arg)) + { + agtype_value *agtv_value; + + agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + + if (agtv_value->type == AGTV_STRING) + { + result = agtv_value->val.string.len; + } + else + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("isEmpty() unsupported argument, expected a List, Map, or String"))); + } + } + else if (AGT_ROOT_IS_ARRAY(agt_arg)) + { + result = AGT_ROOT_COUNT(agt_arg); + } + else if (AGT_ROOT_IS_OBJECT(agt_arg)) + { + result = AGT_ROOT_COUNT(agt_arg); + } + else + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("isEmpty() unsupported argument, expected a List, Map, or String"))); + } + } + else + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("isEmpty() unsupported argument, expected a List, Map, or String"))); + } + + /* build the result */ + PG_RETURN_BOOL(result == 0); +} + PG_FUNCTION_INFO_V1(age_label); /* * Executor function for label(edge/vertex).
