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