You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@age.apache.org by de...@apache.org on 2021/10/07 18:50:22 UTC

[incubator-age] branch master updated: Add openCypher keys() function AGE2-419

This is an automated email from the ASF dual-hosted git repository.

dehowef pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-age.git


The following commit(s) were added to refs/heads/master by this push:
     new 71de14b  Add openCypher keys() function AGE2-419
71de14b is described below

commit 71de14be56d192801d469da82d1df8d3b3193e40
Author: Dehowe Feng <de...@gmail.com>
AuthorDate: Wed Oct 6 16:58:13 2021 -0700

    Add openCypher keys() function AGE2-419
    
    Added  the keys() function. This is ticket AGE2-419.
    
    Added regression tests.
---
 age--0.6.0.sql                 |  11 ++++
 regress/expected/catalog.out   |  12 ++--
 regress/expected/expr.out      |  91 ++++++++++++++++++++++++++-
 regress/sql/expr.sql           |  23 ++++++-
 src/backend/utils/adt/agtype.c | 137 +++++++++++++++++++++++++++++++++++++++++
 5 files changed, 266 insertions(+), 8 deletions(-)

diff --git a/age--0.6.0.sql b/age--0.6.0.sql
index 9a8b957..e675a28 100644
--- a/age--0.6.0.sql
+++ b/age--0.6.0.sql
@@ -3333,6 +3333,17 @@ PARALLEL SAFE
 AS 'MODULE_PATHNAME';
 
 --
+-- List functions -- return a list based on input
+--
+
+CREATE FUNCTION ag_catalog.age_keys(agtype)
+RETURNS agtype
+LANGUAGE c
+STABLE
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+--
 -- Trig functions - radian input
 --
 CREATE FUNCTION ag_catalog.age_sin(variadic "any")
diff --git a/regress/expected/catalog.out b/regress/expected/catalog.out
index f79e217..dae8a0a 100644
--- a/regress/expected/catalog.out
+++ b/regress/expected/catalog.out
@@ -337,10 +337,10 @@ NOTICE:  ELabel "r" has been created
 SELECT * FROM ag_label;
        name       | graph | id | kind |      relation      
 ------------------+-------+----+------+--------------------
- _ag_label_vertex | 17627 |  1 | v    | g._ag_label_vertex
- _ag_label_edge   | 17627 |  2 | e    | g._ag_label_edge
- n                | 17627 |  3 | v    | g.n
- r                | 17627 |  4 | e    | g.r
+ _ag_label_vertex | 17629 |  1 | v    | g._ag_label_vertex
+ _ag_label_edge   | 17629 |  2 | e    | g._ag_label_edge
+ n                | 17629 |  3 | v    | g.n
+ r                | 17629 |  4 | e    | g.r
 (4 rows)
 
 -- try to create duplicate labels
@@ -367,8 +367,8 @@ NOTICE:  label "g"."r" has been dropped
 SELECT * FROM ag_label;
        name       | graph | id | kind |      relation      
 ------------------+-------+----+------+--------------------
- _ag_label_vertex | 17627 |  1 | v    | g._ag_label_vertex
- _ag_label_edge   | 17627 |  2 | e    | g._ag_label_edge
+ _ag_label_vertex | 17629 |  1 | v    | g._ag_label_vertex
+ _ag_label_edge   | 17629 |  2 | e    | g._ag_label_edge
 (2 rows)
 
 -- try to remove labels that is not there
diff --git a/regress/expected/expr.out b/regress/expected/expr.out
index 724bc76..1317aeb 100644
--- a/regress/expected/expr.out
+++ b/regress/expected/expr.out
@@ -5378,7 +5378,84 @@ SELECT * FROM cypher('VLE', $$MATCH (u)-[*..5]-(v) RETURN u, v$$) AS (u agtype,
 ERROR:  variable length relationships are not supported
 LINE 1: SELECT * FROM cypher('VLE', $$MATCH (u)-[*..5]-(v) RETURN u,...
                                                 ^
--- list functions relationships(), range()
+-- list functions relationships(), range(), keys()
+SELECT create_graph('keys');
+NOTICE:  graph "keys" has been created
+ create_graph 
+--------------
+ 
+(1 row)
+
+-- keys()
+SELECT * FROM cypher('keys', $$CREATE ({name: 'hikaru utada', age: 38, job: 'singer'})-[:collaborated_with {song:"face my fears"}]->( {name: 'sonny moore', age: 33, stage_name: 'skrillex', job: 'producer'})$$) AS (result agtype);
+ result 
+--------
+(0 rows)
+
+SELECT * FROM cypher('keys', $$CREATE ({name: 'alexander guy cook', age: 31, stage_name:"a. g. cook", job: 'producer'})$$) AS (result agtype);
+ result 
+--------
+(0 rows)
+
+SELECT * FROM cypher('keys', $$CREATE ({name: 'keiko fuji', age: 62, job: 'singer'})$$) AS (result agtype);
+ result 
+--------
+(0 rows)
+
+SELECT * FROM cypher('keys', $$MATCH (a),(b) WHERE a.name = 'hikaru utada' AND b.name = 'alexander guy cook' CREATE (a)-[:collaborated_with {song:"one last kiss"}]->(b)$$) AS (result agtype);
+ result 
+--------
+(0 rows)
+
+SELECT * FROM cypher('keys', $$MATCH (a),(b) WHERE a.name = 'hikaru utada' AND b.name = 'keiko fuji' CREATE (a)-[:knows]->(b)$$) AS (result agtype);
+ result 
+--------
+(0 rows)
+
+SELECT * FROM cypher('keys', $$MATCH (v) RETURN keys(v)$$) AS (vertex_keys agtype);
+             vertex_keys              
+--------------------------------------
+ ["age", "job", "name"]
+ ["age", "job", "name", "stage_name"]
+ ["age", "job", "name", "stage_name"]
+ ["age", "job", "name"]
+(4 rows)
+
+SELECT * FROM cypher('keys', $$MATCH ()-[e]-() RETURN keys(e)$$) AS (edge_keys agtype);
+ edge_keys 
+-----------
+ ["song"]
+ ["song"]
+ []
+(3 rows)
+
+SELECT * FROM cypher('keys', $$RETURN keys({a:1,b:'two',c:[1,2,3]})$$) AS (keys agtype);
+      keys       
+-----------------
+ ["a", "b", "c"]
+(1 row)
+
+--should return empty list
+SELECT * FROM cypher('keys', $$RETURN keys({})$$) AS (keys agtype);
+ keys 
+------
+ []
+(1 row)
+
+--should return sql null
+SELECT * FROM cypher('keys', $$RETURN keys(null)$$) AS (keys agtype);
+ keys 
+------
+ 
+(1 row)
+
+--should return error
+SELECT * from cypher('keys', $$RETURN keys([1,2,3])$$) as (keys agtype);
+ERROR:  keys() argument must be a vertex, edge, object or null
+SELECT * from cypher('keys', $$RETURN keys("string")$$) as (keys agtype);
+ERROR:  keys() argument must be a vertex, edge, object or null
+SELECT * from cypher('keys', $$MATCH u=()-[]-() RETURN keys(u)$$) as (keys agtype);
+ERROR:  keys() argument must be a vertex, edge, object or null
 SELECT create_graph('list');
 NOTICE:  graph "list" has been created
  create_graph 
@@ -5613,6 +5690,18 @@ NOTICE:  graph "regex" has been dropped
  
 (1 row)
 
+SELECT * FROM drop_graph('keys', true);
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table keys._ag_label_vertex
+drop cascades to table keys._ag_label_edge
+drop cascades to table keys.collaborated_with
+drop cascades to table keys.knows
+NOTICE:  graph "keys" has been dropped
+ drop_graph 
+------------
+ 
+(1 row)
+
 SELECT * FROM drop_graph('list', true);
 NOTICE:  drop cascades to 3 other objects
 DETAIL:  drop cascades to table list._ag_label_vertex
diff --git a/regress/sql/expr.sql b/regress/sql/expr.sql
index 8f1205b..3160a77 100644
--- a/regress/sql/expr.sql
+++ b/regress/sql/expr.sql
@@ -2213,7 +2213,27 @@ SELECT * FROM cypher('VLE', $$MATCH (u)-[*0..1]-(v) RETURN u, v$$) AS (u agtype,
 SELECT * FROM cypher('VLE', $$MATCH (u)-[*..1]-(v) RETURN u, v$$) AS (u agtype, v agtype);
 SELECT * FROM cypher('VLE', $$MATCH (u)-[*..5]-(v) RETURN u, v$$) AS (u agtype, v agtype);
 
--- list functions relationships(), range()
+-- list functions relationships(), range(), keys()
+SELECT create_graph('keys');
+-- keys()
+SELECT * FROM cypher('keys', $$CREATE ({name: 'hikaru utada', age: 38, job: 'singer'})-[:collaborated_with {song:"face my fears"}]->( {name: 'sonny moore', age: 33, stage_name: 'skrillex', job: 'producer'})$$) AS (result agtype);
+SELECT * FROM cypher('keys', $$CREATE ({name: 'alexander guy cook', age: 31, stage_name:"a. g. cook", job: 'producer'})$$) AS (result agtype);
+SELECT * FROM cypher('keys', $$CREATE ({name: 'keiko fuji', age: 62, job: 'singer'})$$) AS (result agtype);
+SELECT * FROM cypher('keys', $$MATCH (a),(b) WHERE a.name = 'hikaru utada' AND b.name = 'alexander guy cook' CREATE (a)-[:collaborated_with {song:"one last kiss"}]->(b)$$) AS (result agtype);
+SELECT * FROM cypher('keys', $$MATCH (a),(b) WHERE a.name = 'hikaru utada' AND b.name = 'keiko fuji' CREATE (a)-[:knows]->(b)$$) AS (result agtype);
+SELECT * FROM cypher('keys', $$MATCH (v) RETURN keys(v)$$) AS (vertex_keys agtype);
+SELECT * FROM cypher('keys', $$MATCH ()-[e]-() RETURN keys(e)$$) AS (edge_keys agtype);
+SELECT * FROM cypher('keys', $$RETURN keys({a:1,b:'two',c:[1,2,3]})$$) AS (keys agtype);
+
+--should return empty list
+SELECT * FROM cypher('keys', $$RETURN keys({})$$) AS (keys agtype);
+--should return sql null
+SELECT * FROM cypher('keys', $$RETURN keys(null)$$) AS (keys agtype);
+--should return error
+SELECT * from cypher('keys', $$RETURN keys([1,2,3])$$) as (keys agtype);
+SELECT * from cypher('keys', $$RETURN keys("string")$$) as (keys agtype);
+SELECT * from cypher('keys', $$MATCH u=()-[]-() RETURN keys(u)$$) as (keys agtype);
+
 SELECT create_graph('list');
 -- relationships()
 SELECT * from cypher('list', $$CREATE p=()-[:knows]->() RETURN p$$) as (path agtype);
@@ -2259,6 +2279,7 @@ SELECT * FROM drop_graph('group_by', true);
 SELECT * FROM drop_graph('UCSC', true);
 SELECT * FROM drop_graph('expr', true);
 SELECT * FROM drop_graph('regex', true);
+SELECT * FROM drop_graph('keys', true);
 SELECT * FROM drop_graph('list', true);
 
 --
diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c
index ec01445..80c0cd8 100644
--- a/src/backend/utils/adt/agtype.c
+++ b/src/backend/utils/adt/agtype.c
@@ -156,6 +156,10 @@ static agtype_value *integer_to_agtype_value(int64 int_value);
 static int64 get_int64_from_int_datums(Datum d, Oid type, char *funcname,
                                        bool *is_agnull);
 
+static agtype_iterator *get_next_object_key(agtype_iterator *it,
+                                             agtype_container *agtc,
+                                             agtype_value *key);
+
 PG_FUNCTION_INFO_V1(agtype_in);
 
 /*
@@ -8284,6 +8288,139 @@ Datum age_eq_tilde(PG_FUNCTION_ARGS)
                     errmsg("agtype string values expected")));
 }
 
+/* helper function to step through and retrieve keys from an object.
+ * borrowed and modified from get_next_object_pair() in agtype_vle.c
+*/
+static agtype_iterator *get_next_object_key(agtype_iterator *it,
+                                             agtype_container *agtc,
+                                             agtype_value *key)
+{
+    agtype_iterator_token itok;
+    agtype_value tmp;
+
+    /* verify input params */
+    Assert(agtc != NULL);
+    Assert(key != NULL);
+
+    /* check to see if the container is empty */
+    if (AGTYPE_CONTAINER_SIZE(agtc) == 0)
+    {
+        return NULL;
+    }
+
+    /* if the passed iterator is NULL, this is the first time, create it */
+    if (it == NULL)
+    {
+        /* initial the iterator */
+        it = agtype_iterator_init(agtc);
+        /* get the first token */
+        itok = agtype_iterator_next(&it, &tmp, false);
+        /* it should be WAGT_BEGIN_OBJECT */
+        Assert(itok == WAGT_BEGIN_OBJECT);
+    }
+
+    /* the next token should be a key or the end of the object */
+    itok = agtype_iterator_next(&it, &tmp, false);
+    Assert(itok == WAGT_KEY || WAGT_END_OBJECT);
+    /* if this is the end of the object return NULL */
+    if (itok == WAGT_END_OBJECT)
+    {
+        return NULL;
+    }
+
+    /* this should be the key, copy it */
+    if (itok == WAGT_KEY)
+    {
+        memcpy(key, &tmp, sizeof(agtype_value));
+    }
+
+    /*
+     * The next token should be a value but, it could be a begin tokens for
+     * arrays or objects. For those we just return NULL to ignore them.
+     */
+    itok = agtype_iterator_next(&it, &tmp, true);
+    Assert(itok == WAGT_VALUE);
+
+    /* return the iterator */
+    return it;
+}
+
+PG_FUNCTION_INFO_V1(age_keys);
+/*
+ * Execution function to implement openCypher keys() function
+ */
+Datum age_keys(PG_FUNCTION_ARGS)
+{
+    agtype *agt_arg = NULL;
+    agtype_value *agtv_result = NULL;
+    agtype_value obj_key = {0};
+    agtype_iterator *it = NULL;
+    agtype_parse_state *parse_state = NULL;
+
+    /* check for null */
+    if (PG_ARGISNULL(0))
+    {
+        PG_RETURN_NULL();
+    }
+
+    //needs to be a map, node, or relationship
+    agt_arg = AG_GET_ARG_AGTYPE_P(0);
+
+    /*
+     * check for a scalar object. edges and vertexes are scalar, objects are not
+     * scalar and will be handled separately
+     */
+    if (AGT_ROOT_IS_SCALAR(agt_arg))
+    {
+        agtv_result = get_ith_agtype_value_from_container(&agt_arg->root, 0);
+
+        /* is it an agtype null, return null if it is */
+        if (agtv_result->type == AGTV_NULL)
+            PG_RETURN_NULL();
+
+        /* check for proper agtype and extract the properties field */
+        if (agtv_result->type == AGTV_EDGE ||
+            agtv_result->type == AGTV_VERTEX)
+        {
+            agtv_result = get_agtype_value_object_value(agtv_result, "properties");
+
+            Assert(agtv_result != NULL);
+            Assert(agtv_result->type = AGTV_OBJECT);
+        }
+        else
+        {
+            ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                    errmsg("keys() argument must be a vertex, edge, object or null")));
+        }
+
+        agt_arg = agtype_value_to_agtype(agtv_result);
+        agtv_result = NULL;
+    }
+    else if (!AGT_ROOT_IS_OBJECT(agt_arg))
+    {
+        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                errmsg("keys() argument must be a vertex, edge, object or null")));
+    }
+
+    /* push the beginning of the array */
+    agtv_result = push_agtype_value(&parse_state, WAGT_BEGIN_ARRAY, NULL);
+
+    /* populate the array with keys */
+    while ((it = get_next_object_key(it, &agt_arg->root, &obj_key)))
+    {
+        agtv_result = push_agtype_value(&parse_state, WAGT_ELEM, &obj_key);
+    }
+
+    /* push the end of the array*/
+    agtv_result = push_agtype_value(&parse_state, WAGT_END_ARRAY, NULL);
+
+    Assert(agtv_result != NULL);
+    Assert(agtv_result->type = AGTV_ARRAY);
+
+    PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result));
+
+}
+
 PG_FUNCTION_INFO_V1(age_relationships);
 /*
  * Execution function to implement openCypher relationships() function