You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@age.apache.org by jg...@apache.org on 2023/02/27 20:23:38 UTC
[age] branch master updated: Implement isEmpty() predicate function (#710)
This is an automated email from the ASF dual-hosted git repository.
jgemignani pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/age.git
The following commit(s) were added to refs/heads/master by this push:
new 6e8836b1 Implement isEmpty() predicate function (#710)
6e8836b1 is described below
commit 6e8836b1df0047410944ab78f33d9f62a1cee669
Author: Muhammad Taha Naveed <85...@users.noreply.github.com>
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.
---
age--1.2.0.sql | 8 ++
regress/expected/cypher_match.out | 160 ++++++++++++++++++++++++++++++++++++++
regress/sql/cypher_match.sql | 97 +++++++++++++++++++++++
src/backend/utils/adt/agtype.c | 81 +++++++++++++++++++
4 files changed, 346 insertions(+)
diff --git a/age--1.2.0.sql b/age--1.2.0.sql
index e34b3c97..e50f1a62 100644
--- a/age--1.2.0.sql
+++ b/age--1.2.0.sql
@@ -3551,6 +3551,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/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 22bf462c..ed4bf4d4 100644
--- a/src/backend/utils/adt/agtype.c
+++ b/src/backend/utils/adt/agtype.c
@@ -5508,6 +5508,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).