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).

Reply via email to