diff --git a/regress/expected/cypher_set.out b/regress/expected/cypher_set.out index 0310ae015..1b391a0f3 100644 --- a/regress/expected/cypher_set.out +++ b/regress/expected/cypher_set.out @@ -787,6 +787,11 @@ SELECT * FROM cypher('cypher_set_1', $$ CREATE (a:Robert {name:'Robert', role:'m --- (0 rows) +SELECT * FROM cypher('cypher_set_1',$$ CREATE (a: VertexA {map: {a: 1, b: {c: 2, d: []}, c: [{d: -100, e: []}]}, list: [1, "string", [{a: []}, [[1, 2]]]], bool: true, num: -1.9::numeric, str: "string"})$$) as (a agtype); + a +--- +(0 rows) + -- test copying properties between entities SELECT * FROM cypher('cypher_set_1', $$ MATCH (at {name: 'Andy'}), (pn {name: 'Peter'}) @@ -869,6 +874,47 @@ $$) AS (p agtype); {"id": 2251799813685249, "label": "Robert", "properties": {"age": 47, "city": "London", "name": "Rob"}}::vertex (1 row) +-- test plus-equal with original properties having non-scalar values +SELECT * FROM cypher('cypher_set_1', $$ + MATCH (p {map: {}}) + SET p += {json: {a: -1, b: ['a', -1, true], c: {d: 'string'}}} + RETURN p +$$) AS (p agtype); + p +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"id": 2533274790395905, "label": "VertexA", "properties": {"map": {"a": 1, "b": {"c": 2, "d": []}, "c": [{"d": -100, "e": []}]}, "num": -1.9::numeric, "str": "string", "bool": true, "json": {"a": -1, "b": ["a", -1, true], "c": {"d": "string"}}, "list": [1, "string", [{"a": []}, [[1, 2]]]]}}::vertex +(1 row) + +SELECT * FROM cypher('cypher_set_1', $$ + MATCH (p: VertexA {map: {}}) + SET p += {list_upd: [1, 2, 3, 4, 5]} + RETURN p +$$) AS (p agtype); + p +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"id": 2533274790395905, "label": "VertexA", "properties": {"map": {"a": 1, "b": {"c": 2, "d": []}, "c": [{"d": -100, "e": []}]}, "num": -1.9::numeric, "str": "string", "bool": true, "json": {"a": -1, "b": ["a", -1, true], "c": {"d": "string"}}, "list": [1, "string", [{"a": []}, [[1, 2]]]], "list_upd": [1, 2, 3, 4, 5]}}::vertex +(1 row) + +SELECT * FROM cypher('cypher_set_1', $$ + MATCH (p: VertexA) + SET p += {vertex: {id: 281474976710659, label: "", properties: {a: 1, b: [1, 2, 3]}}::vertex} + RETURN p +$$) AS (p agtype); + p +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"id": 2533274790395905, "label": "VertexA", "properties": {"map": {"a": 1, "b": {"c": 2, "d": []}, "c": [{"d": -100, "e": []}]}, "num": -1.9::numeric, "str": "string", "bool": true, "json": {"a": -1, "b": ["a", -1, true], "c": {"d": "string"}}, "list": [1, "string", [{"a": []}, [[1, 2]]]], "vertex": {"id": 281474976710659, "label": "", "properties": {"a": 1, "b": [1, 2, 3]}}::vertex, "list_upd": [1, 2, 3, 4, 5]}}::vertex +(1 row) + +SELECT * FROM cypher('cypher_set_1', $$ + MATCH (p {map: {}}) + SET p += {} + RETURN p +$$) AS (p agtype); + p +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"id": 2533274790395905, "label": "VertexA", "properties": {"map": {"a": 1, "b": {"c": 2, "d": []}, "c": [{"d": -100, "e": []}]}, "num": -1.9::numeric, "str": "string", "bool": true, "json": {"a": -1, "b": ["a", -1, true], "c": {"d": "string"}}, "list": [1, "string", [{"a": []}, [[1, 2]]]], "vertex": {"id": 281474976710659, "label": "", "properties": {"a": 1, "b": [1, 2, 3]}}::vertex, "list_upd": [1, 2, 3, 4, 5]}}::vertex +(1 row) + -- -- Check passing mismatched types with SET -- Issue 899 @@ -912,7 +958,7 @@ NOTICE: graph "cypher_set" has been dropped (1 row) SELECT drop_graph('cypher_set_1', true); -NOTICE: drop cascades to 8 other objects +NOTICE: drop cascades to 9 other objects DETAIL: drop cascades to table cypher_set_1._ag_label_vertex drop cascades to table cypher_set_1._ag_label_edge drop cascades to table cypher_set_1."Andy" @@ -921,6 +967,7 @@ drop cascades to table cypher_set_1."Kevin" drop cascades to table cypher_set_1."Matt" drop cascades to table cypher_set_1."Juan" drop cascades to table cypher_set_1."Robert" +drop cascades to table cypher_set_1."VertexA" NOTICE: graph "cypher_set_1" has been dropped drop_graph ------------ diff --git a/regress/sql/cypher_set.sql b/regress/sql/cypher_set.sql index 484cbff7d..6720d6de4 100644 --- a/regress/sql/cypher_set.sql +++ b/regress/sql/cypher_set.sql @@ -260,6 +260,7 @@ SELECT * FROM cypher('cypher_set_1', $$ CREATE (a:Kevin {name:'Kevin', age:32, h SELECT * FROM cypher('cypher_set_1', $$ CREATE (a:Matt {name:'Matt', city:'Toronto'}) $$) AS (a agtype); SELECT * FROM cypher('cypher_set_1', $$ CREATE (a:Juan {name:'Juan', role:'admin'}) $$) AS (a agtype); SELECT * FROM cypher('cypher_set_1', $$ CREATE (a:Robert {name:'Robert', role:'manager', city:'London'}) $$) AS (a agtype); +SELECT * FROM cypher('cypher_set_1',$$ CREATE (a: VertexA {map: {a: 1, b: {c: 2, d: []}, c: [{d: -100, e: []}]}, list: [1, "string", [{a: []}, [[1, 2]]]], bool: true, num: -1.9::numeric, str: "string"})$$) as (a agtype); -- test copying properties between entities SELECT * FROM cypher('cypher_set_1', $$ @@ -316,6 +317,31 @@ SELECT * FROM cypher('cypher_set_1', $$ RETURN p $$) AS (p agtype); +-- test plus-equal with original properties having non-scalar values +SELECT * FROM cypher('cypher_set_1', $$ + MATCH (p {map: {}}) + SET p += {json: {a: -1, b: ['a', -1, true], c: {d: 'string'}}} + RETURN p +$$) AS (p agtype); + +SELECT * FROM cypher('cypher_set_1', $$ + MATCH (p: VertexA {map: {}}) + SET p += {list_upd: [1, 2, 3, 4, 5]} + RETURN p +$$) AS (p agtype); + +SELECT * FROM cypher('cypher_set_1', $$ + MATCH (p: VertexA) + SET p += {vertex: {id: 281474976710659, label: "", properties: {a: 1, b: [1, 2, 3]}}::vertex} + RETURN p +$$) AS (p agtype); + +SELECT * FROM cypher('cypher_set_1', $$ + MATCH (p {map: {}}) + SET p += {} + RETURN p +$$) AS (p agtype); + -- -- Check passing mismatched types with SET -- Issue 899 diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index 5d5c70377..53ce14968 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -9209,22 +9209,14 @@ agtype_value *alter_properties(agtype_value *original_properties, // Copy original properties. if (original_properties) { - int i; - if (original_properties->type != AGTV_OBJECT) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("a map is expected"))); } - for (i = 0; i < original_properties->val.object.num_pairs; i++) - { - agtype_pair* pair = original_properties->val.object.pairs + i; - parsed_agtype_value = push_agtype_value(&parse_state, WAGT_KEY, - &pair->key); - parsed_agtype_value = push_agtype_value(&parse_state, WAGT_VALUE, - &pair->value); - } + copy_agtype_value(parse_state, original_properties, + &parsed_agtype_value, true); } // Append new properties. diff --git a/src/backend/utils/adt/agtype_util.c b/src/backend/utils/adt/agtype_util.c index 0583bce6b..107d2d0df 100644 --- a/src/backend/utils/adt/agtype_util.c +++ b/src/backend/utils/adt/agtype_util.c @@ -2385,3 +2385,105 @@ void pfree_agtype_in_state(agtype_in_state* value) pfree_agtype_value(value->res); free(value->parse_state); } + +/* + * helper function that recursively unpacks the agtype_value to be copied + * and pushes the scalar values into the copied agtype_value. + * this helps skip the serialization part at some places where the original + * properties passed to the function are in agtype_value format and + * converting it to agtype for iteration can be expensive. + * the caller of this function will need to push start and end object tokens + * on its own as this function might be used in places where pushing only start + * object token at top level is required (for example in alter_properties) + */ +void copy_agtype_value(agtype_parse_state* pstate, + agtype_value* original_agtype_value, + agtype_value **copied_agtype_value, bool is_top_level) +{ + int i = 0; + + /* + * guards against stack overflow due to deeply nested agtype_value + */ + check_stack_depth(); + + /* + * directly pass the agtype_value to be pushed into the copied result + * if type is scalar or binary (array or object) as push_agtype_value + * can unpack binary on its own + */ + if (IS_A_AGTYPE_SCALAR(original_agtype_value) || + original_agtype_value->type == AGTV_BINARY) + { + *copied_agtype_value = push_agtype_value(&pstate, WAGT_ELEM, + original_agtype_value); + } + /* + * if the passed in type is object or array, unpack it + * until we are left with a scalar value to push to copied result + */ + else if (original_agtype_value->type == AGTV_OBJECT) + { + if (!is_top_level) + { + *copied_agtype_value = push_agtype_value(&pstate, + WAGT_BEGIN_OBJECT, + NULL); + } + + for (; i < original_agtype_value->val.object.num_pairs; i ++) + { + agtype_pair *pair = original_agtype_value->val.object.pairs + i; + *copied_agtype_value = push_agtype_value(&pstate, WAGT_KEY, + &pair->key); + + if (IS_A_AGTYPE_SCALAR(&pair->value)) + { + *copied_agtype_value = push_agtype_value(&pstate, WAGT_VALUE, + &pair->value); + } + else + { + /* do a recursive call once a non-scalar value is reached */ + copy_agtype_value(pstate, &pair->value, copied_agtype_value, + false); + } + } + + if (!is_top_level) + { + *copied_agtype_value = push_agtype_value(&pstate, WAGT_END_OBJECT, + NULL); + } + } + else if (original_agtype_value->type == AGTV_ARRAY) + { + *copied_agtype_value = push_agtype_value(&pstate, WAGT_BEGIN_ARRAY, + NULL); + + for (; i < original_agtype_value->val.array.num_elems; i++) + { + agtype_value elem = original_agtype_value->val.array.elems[i]; + + if (IS_A_AGTYPE_SCALAR(&elem)) + { + *copied_agtype_value = push_agtype_value(&pstate, WAGT_ELEM, + &elem); + } + else + { + /* do a recursive call once a non-scalar value is reached */ + copy_agtype_value(pstate, &elem, copied_agtype_value, false); + } + } + + *copied_agtype_value = push_agtype_value(&pstate, WAGT_END_ARRAY, + NULL); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid type provided for copy_agtype_value"))); + } +} \ No newline at end of file diff --git a/src/include/utils/agtype.h b/src/include/utils/agtype.h index 436470ef1..7020ac8e4 100644 --- a/src/include/utils/agtype.h +++ b/src/include/utils/agtype.h @@ -489,6 +489,9 @@ void convert_extended_object(StringInfo buffer, agtentry *pheader, agtype_value *val); Datum get_numeric_datum_from_agtype_value(agtype_value *agtv); bool is_numeric_result(agtype_value *lhs, agtype_value *rhs); +void copy_agtype_value(agtype_parse_state* pstate, + agtype_value* original_agtype_value, + agtype_value **copied_agtype_value, bool is_top_level); /* agtype.c support functions */ /*