This is an automated email from the ASF dual-hosted git repository. jgemignani pushed a commit to branch PG15 in repository https://gitbox.apache.org/repos/asf/age.git
commit c2faa133308b79d579507003fc790c0f00045500 Author: Muhammad Taha Naveed <mt...@apache.org> AuthorDate: Tue Jun 10 23:07:49 2025 +0500 Reimplement list comprehension (#2169) * Revert "Fix issue 1955 - List comprehension in WHERE clause (#2094)" This reverts commit 0f0d9be9ba02fb90272d2053986f2b5aa4a0c25c. * Revert "Fix error using list comprehension with WITH * (#1838)" This reverts commit 5e08a2f58693adca55085da8d56eb1831d963d20. * Revert "Fix shift/reduce conflict in grammar (#1719)" This reverts commit fab3119a109280fd63237ce17c6d4dd60b7dfc03. * Revert "Implement list comprehension (#1610)" This reverts commit 3b2b394eb669c4f80fc893ad224cf5ea4e10c5a9. * Reimplement list comprehension - Reimplement list comprehension to use ARRAY sublinks. - Some test results were not correct. All the test results that are modified are correct and are verified with neo4j. - Also resolves the issue of using list comprehension in MERGE clause (1611) and issue 1850 * Add expression tree walkers for cypher nodes - Added cypher_raw_expr_tree_walker and cypher_expr_tree_walker, based on Postgres's raw_expression_tree_walker and expression_tree_walker. These follow the same walker API as Postgres and add support for Cypher-specific nodes. - Also added the agtype[] to agtype func and typecast to 1.5.0-y.y.y.sql - Simplifies logic for cypher_subquery handling in where clause. - Fixes a crash when list comprehension in the WHERE clause references a variable from the preceding MATCH clause. --- age--1.5.0--y.y.y.sql | 13 +- regress/expected/list_comprehension.out | 102 ++++++-- regress/sql/list_comprehension.sql | 16 +- sql/agtype_coercions.sql | 11 + sql/agtype_typecast.sql | 3 +- src/backend/nodes/ag_nodes.c | 2 + src/backend/nodes/cypher_outfuncs.c | 14 +- src/backend/optimizer/cypher_pathnode.c | 82 ++++++- src/backend/parser/cypher_analyze.c | 416 +++++++++----------------------- src/backend/parser/cypher_clause.c | 319 ++++++++++++------------ src/backend/parser/cypher_expr.c | 67 +---- src/backend/parser/cypher_gram.y | 148 ++++-------- src/backend/parser/cypher_item.c | 242 +------------------ src/backend/utils/adt/agtype.c | 55 +---- src/include/nodes/ag_nodes.h | 2 +- src/include/nodes/cypher_nodes.h | 14 +- src/include/nodes/cypher_outfuncs.h | 1 + src/include/parser/cypher_analyze.h | 12 +- src/include/parser/cypher_clause.h | 9 - src/include/parser/cypher_item.h | 2 - src/include/parser/cypher_parse_node.h | 1 - 21 files changed, 570 insertions(+), 961 deletions(-) diff --git a/age--1.5.0--y.y.y.sql b/age--1.5.0--y.y.y.sql index 1d4aa602..5bf45a2a 100644 --- a/age--1.5.0--y.y.y.sql +++ b/age--1.5.0--y.y.y.sql @@ -156,4 +156,15 @@ PARALLEL SAFE AS 'MODULE_PATHNAME'; CREATE CAST (agtype AS json) - WITH FUNCTION ag_catalog.agtype_to_json(agtype); \ No newline at end of file + WITH FUNCTION ag_catalog.agtype_to_json(agtype); + +CREATE FUNCTION ag_catalog.agtype_array_to_agtype(agtype[]) + RETURNS agtype + LANGUAGE c + IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (agtype[] AS agtype) + WITH FUNCTION ag_catalog.agtype_array_to_agtype(agtype[]); \ No newline at end of file diff --git a/regress/expected/list_comprehension.out b/regress/expected/list_comprehension.out index bf5731d2..07f77770 100644 --- a/regress/expected/list_comprehension.out +++ b/regress/expected/list_comprehension.out @@ -166,9 +166,9 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH p=(u) RETURN [i IN range(0, SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list] $$) AS (result agtype); result ------------------------- + [0, 2, 4, 6, 8, 10, 12] [1, 3, 5, 7, 9, 11, 13] [] - [0, 2, 4, 6, 8, 10, 12] (3 rows) SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list WHERE i % 3 = 0] $$) AS (result agtype); @@ -176,35 +176,40 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list WHER ------------ [0, 6, 12] [3, 9] -(2 rows) + [] +(3 rows) SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list WHERE i % 3 = 0 | i/3] $$) AS (result agtype); result ----------- [0, 2, 4] [1, 3] -(2 rows) + [] +(3 rows) SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list WHERE i % 3 = 0 | i/3][1] $$) AS (result agtype); result -------- 2 3 -(2 rows) + +(3 rows) SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list WHERE i % 3 = 0 | i/3][0..2] $$) AS (result agtype); result -------- [0, 2] [1, 3] -(2 rows) + [] +(3 rows) SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list WHERE i % 3 = 0 | i/3][0..2][1] $$) AS (result agtype); result -------- 2 3 -(2 rows) + +(3 rows) -- Nested cases SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3]]] $$) AS (result agtype); @@ -299,9 +304,9 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list@>[i IN rang SELECT * FROM cypher('list_comprehension', $$ MATCH (u) MATCH (v) WHERE v.list=[i IN u.list] RETURN v $$) AS (result agtype); result ----------------------------------------------------------------------------------------------- - {"id": 281474976710659, "label": "", "properties": {"list": []}}::vertex {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 10, 12]}}::vertex {"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 11, 13]}}::vertex + {"id": 281474976710659, "label": "", "properties": {"list": []}}::vertex (3 rows) SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list:[i IN range(0,12,2)]}) RETURN u $$) AS (result agtype); @@ -330,11 +335,11 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list:[i IN range(1,13,2) SELECT * FROM cypher('list_comprehension', $$ MATCH (u) MATCH (v {list:[i IN u.list]}) RETURN v $$) AS (result agtype); result ----------------------------------------------------------------------------------------------- - {"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 11, 13]}}::vertex {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 10, 12]}}::vertex - {"id": 281474976710659, "label": "", "properties": {"list": []}}::vertex + {"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 11, 13]}}::vertex {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 10, 12]}}::vertex {"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 11, 13]}}::vertex + {"id": 281474976710659, "label": "", "properties": {"list": []}}::vertex (5 rows) SELECT * FROM cypher('list_comprehension', $$ CREATE (u {list:[i IN range(12,24,2)]}) RETURN u $$) AS (result agtype); @@ -371,26 +376,26 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH(u) WITH u WHERE u.list = [u SELECT * FROM cypher('list_comprehension', $$ MATCH(u) WITH u WHERE u.list = [u IN [0, 2, 4, 6, 8, 10, 12]] OR size(u.list) = 0 RETURN u $$) AS (u agtype); u ----------------------------------------------------------------------------------------------- - {"id": 281474976710659, "label": "", "properties": {"list": []}}::vertex {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 10, 12]}}::vertex + {"id": 281474976710659, "label": "", "properties": {"list": []}}::vertex (2 rows) SELECT * FROM cypher('list_comprehension', $$ MATCH(u) WITH u WHERE u.list = [u IN [0, 2, 4, 6, 8, 10, 12]] OR size(u.list)::bool RETURN u $$) AS (u agtype); u -------------------------------------------------------------------------------------------------------- + {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 10, 12]}}::vertex {"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 11, 13]}}::vertex - {"id": 281474976710661, "label": "", "properties": {"list": [6, 8, 10, 12]}}::vertex {"id": 281474976710660, "label": "", "properties": {"list": [12, 14, 16, 18, 20, 22, 24]}}::vertex + {"id": 281474976710661, "label": "", "properties": {"list": [6, 8, 10, 12]}}::vertex {"id": 281474976710662, "label": "", "properties": {"list": [25.0, 49.0, 81.0, 121.0, 169.0]}}::vertex - {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 10, 12]}}::vertex {"id": 281474976710663, "label": "", "properties": {"list": [1, 2, 3]}}::vertex (6 rows) SELECT * FROM cypher('list_comprehension', $$ MATCH(u) WITH u WHERE u.list = [u IN [0, 2, 4, 6, 8, 10, 12]] OR NOT size(u.list)::bool RETURN u $$) AS (u agtype); u ----------------------------------------------------------------------------------------------- - {"id": 281474976710659, "label": "", "properties": {"list": []}}::vertex {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 10, 12]}}::vertex + {"id": 281474976710659, "label": "", "properties": {"list": []}}::vertex (2 rows) SELECT * FROM cypher('list_comprehension', $$ CREATE(u:csm_match {list: ['abc', 'def', 'ghi']}) $$) AS (u agtype); @@ -473,9 +478,6 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH(u {list: [0, 2, 4, 6, 8, 10, ERROR: Invalid use of aggregation in this context LINE 1: ..., $$ MATCH(u {list: [0, 2, 4, 6, 8, 10, 12]}) SET u.c = coll... ^ --- Known issue -SELECT * FROM cypher('list_comprehension', $$ MERGE (u {list:[i IN [1,2,3]]}) RETURN u $$) AS (result agtype); -ERROR: Aggref found in non-Agg plan node -- List comprehension variable scoping SELECT * FROM cypher('list_comprehension', $$ WITH 1 AS m, [m IN [1, 2, 3]] AS n RETURN [m IN [1, 2, 3]] $$) AS (result agtype); result @@ -622,12 +624,12 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list=[i IN u.lis result --------------------------------------------------------------------------------------------------------------------------------------------------------------- {"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 11, 13]}}::vertex + {"id": 281474976710659, "label": "", "properties": {"list": []}}::vertex {"id": 281474976710660, "label": "", "properties": {"list": [12, 14, 16, 18, 20, 22, 24]}}::vertex + {"id": 281474976710661, "label": "", "properties": {"list": [6, 8, 10, 12]}}::vertex {"id": 281474976710662, "label": "", "properties": {"list": [25.0, 49.0, 81.0, 121.0, 169.0]}}::vertex - {"id": 281474976710657, "label": "", "properties": {"a": [], "b": [0, 1, 2, 3, 4, 5], "c": [0, 2, 4, 6, 8, 10, 12], "list": [0, 2, 4, 6, 8, 10, 12]}}::vertex {"id": 281474976710663, "label": "", "properties": {"list": [1, 2, 3]}}::vertex - {"id": 281474976710659, "label": "", "properties": {"list": []}}::vertex - {"id": 281474976710661, "label": "", "properties": {"list": [6, 8, 10, 12]}}::vertex + {"id": 281474976710657, "label": "", "properties": {"a": [], "b": [0, 1, 2, 3, 4, 5], "c": [0, 2, 4, 6, 8, 10, 12], "list": [0, 2, 4, 6, 8, 10, 12]}}::vertex {"id": 844424930131969, "label": "csm_match", "properties": {"list": ["abc", "def", "ghi"]}}::vertex (8 rows) @@ -635,11 +637,12 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list=[i IN u.lis result -------------------------------------------------------------------------------------------------------- {"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 11, 13]}}::vertex + {"id": 281474976710659, "label": "", "properties": {"list": []}}::vertex {"id": 281474976710660, "label": "", "properties": {"list": [12, 14, 16, 18, 20, 22, 24]}}::vertex + {"id": 281474976710661, "label": "", "properties": {"list": [6, 8, 10, 12]}}::vertex {"id": 281474976710662, "label": "", "properties": {"list": [25.0, 49.0, 81.0, 121.0, 169.0]}}::vertex {"id": 281474976710663, "label": "", "properties": {"list": [1, 2, 3]}}::vertex - {"id": 281474976710661, "label": "", "properties": {"list": [6, 8, 10, 12]}}::vertex -(5 rows) +(6 rows) SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE size([e in u.list where e starts with "a"])>0 RETURN u $$) AS (result agtype); result @@ -657,11 +660,64 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH (u ={list:[i IN u.list WHERE result -------------------------------------------------------------------------------------------------------- {"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 11, 13]}}::vertex + {"id": 281474976710659, "label": "", "properties": {"list": []}}::vertex {"id": 281474976710660, "label": "", "properties": {"list": [12, 14, 16, 18, 20, 22, 24]}}::vertex + {"id": 281474976710661, "label": "", "properties": {"list": [6, 8, 10, 12]}}::vertex {"id": 281474976710662, "label": "", "properties": {"list": [25.0, 49.0, 81.0, 121.0, 169.0]}}::vertex {"id": 281474976710663, "label": "", "properties": {"list": [1, 2, 3]}}::vertex - {"id": 281474976710661, "label": "", "properties": {"list": [6, 8, 10, 12]}}::vertex -(5 rows) +(6 rows) + +-- List comprehension in MERGE +SELECT * FROM cypher('list_comprehension', $$ MERGE (u {list:[i IN [1,2,3]]}) RETURN u $$) AS (result agtype); + result +--------------------------------------------------------------------------------- + {"id": 281474976710663, "label": "", "properties": {"list": [1, 2, 3]}}::vertex +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ MERGE (u ={list:[i IN [1,2,3] WHERE i>1]}) RETURN u $$) AS (result agtype); + result +------------------------------------------------------------------------------ + {"id": 281474976710666, "label": "", "properties": {"list": [2, 3]}}::vertex +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ MERGE (u ={list:[i IN [1,2,3] WHERE i>1 | i^2]}) RETURN u $$) AS (result agtype); + result +---------------------------------------------------------------------------------- + {"id": 281474976710667, "label": "", "properties": {"list": [4.0, 9.0]}}::vertex +(1 row) + +-- Issue 1850 +SELECT * FROM cypher('list_comprehension', $$ CREATE (u {list: [0, 2, 4, 6, 8, 10, 12]}) $$) AS (result agtype); + result +-------- +(0 rows) + +SELECT * FROM cypher('list_comprehension', $$ WITH [1, 2, 3] AS u UNWIND collect(u) AS v RETURN v $$) AS (result agtype); +ERROR: Invalid use of aggregation in this context +LINE 1: ...ist_comprehension', $$ WITH [1, 2, 3] AS u UNWIND collect(u)... + ^ +SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list: [0, 2, 4, 6, 8, 10, 12]}) WITH u, collect(u.list) AS v SET u += {b: [u IN range(0, 5)]} SET u.c = [u IN v[0]] RETURN u $$) AS (result agtype); + result +--------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"id": 281474976710657, "label": "", "properties": {"a": [], "b": [0, 1, 2, 3, 4, 5], "c": [0, 2, 4, 6, 8, 10, 12], "list": [0, 2, 4, 6, 8, 10, 12]}}::vertex + {"id": 281474976710668, "label": "", "properties": {"b": [0, 1, 2, 3, 4, 5], "c": [0, 2, 4, 6, 8, 10, 12], "list": [0, 2, 4, 6, 8, 10, 12]}}::vertex +(2 rows) + +SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list: [0, 2, 4, 6, 8, 10, 12]}) SET u.c = collect(u.list) RETURN u $$) AS (u agtype); +ERROR: Invalid use of aggregation in this context +LINE 1: ... $$ MATCH (u {list: [0, 2, 4, 6, 8, 10, 12]}) SET u.c = coll... + ^ +SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list: [0, 2, 4, 6, 8, 10, 12]}) WHERE u.list = [u IN [1, u]] RETURN u $$) AS (u agtype); + u +--- +(0 rows) + +SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list: [0, 2, 4, 6, 8, 10, 12]}) WHERE u.list IN [u IN [1, u.list]] RETURN u $$) AS (u agtype); + u +--------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"id": 281474976710657, "label": "", "properties": {"a": [], "b": [0, 1, 2, 3, 4, 5], "c": [0, 2, 4, 6, 8, 10, 12], "list": [0, 2, 4, 6, 8, 10, 12]}}::vertex + {"id": 281474976710668, "label": "", "properties": {"b": [0, 1, 2, 3, 4, 5], "c": [0, 2, 4, 6, 8, 10, 12], "list": [0, 2, 4, 6, 8, 10, 12]}}::vertex +(2 rows) -- Clean up SELECT * FROM drop_graph('list_comprehension', true); diff --git a/regress/sql/list_comprehension.sql b/regress/sql/list_comprehension.sql index cb941a61..572b2e6b 100644 --- a/regress/sql/list_comprehension.sql +++ b/regress/sql/list_comprehension.sql @@ -117,9 +117,6 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH(u {list: [0, 2, 4, 6, 8, 10, -- invalid use of aggregation in SET SELECT * FROM cypher('list_comprehension', $$ MATCH(u {list: [0, 2, 4, 6, 8, 10, 12]}) SET u.c = collect(u.list) RETURN u $$) AS (u agtype); --- Known issue -SELECT * FROM cypher('list_comprehension', $$ MERGE (u {list:[i IN [1,2,3]]}) RETURN u $$) AS (result agtype); - -- List comprehension variable scoping SELECT * FROM cypher('list_comprehension', $$ WITH 1 AS m, [m IN [1, 2, 3]] AS n RETURN [m IN [1, 2, 3]] $$) AS (result agtype); SELECT * FROM cypher('list_comprehension', $$ WITH 1 AS m RETURN [m IN [1, 2, 3]], m $$) AS (result agtype, result2 agtype); @@ -164,5 +161,18 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE size([e in u.list SELECT * FROM cypher('list_comprehension', $$ MATCH (u ={list:[i IN u.list | i+1]}) RETURN u $$) AS (result agtype); SELECT * FROM cypher('list_comprehension', $$ MATCH (u ={list:[i IN u.list WHERE i>0]}) RETURN u$$) AS (result agtype); +-- List comprehension in MERGE +SELECT * FROM cypher('list_comprehension', $$ MERGE (u {list:[i IN [1,2,3]]}) RETURN u $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MERGE (u ={list:[i IN [1,2,3] WHERE i>1]}) RETURN u $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MERGE (u ={list:[i IN [1,2,3] WHERE i>1 | i^2]}) RETURN u $$) AS (result agtype); + +-- Issue 1850 +SELECT * FROM cypher('list_comprehension', $$ CREATE (u {list: [0, 2, 4, 6, 8, 10, 12]}) $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ WITH [1, 2, 3] AS u UNWIND collect(u) AS v RETURN v $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list: [0, 2, 4, 6, 8, 10, 12]}) WITH u, collect(u.list) AS v SET u += {b: [u IN range(0, 5)]} SET u.c = [u IN v[0]] RETURN u $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list: [0, 2, 4, 6, 8, 10, 12]}) SET u.c = collect(u.list) RETURN u $$) AS (u agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list: [0, 2, 4, 6, 8, 10, 12]}) WHERE u.list = [u IN [1, u]] RETURN u $$) AS (u agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list: [0, 2, 4, 6, 8, 10, 12]}) WHERE u.list IN [u IN [1, u.list]] RETURN u $$) AS (u agtype); + -- Clean up SELECT * FROM drop_graph('list_comprehension', true); \ No newline at end of file diff --git a/sql/agtype_coercions.sql b/sql/agtype_coercions.sql index bdc33af8..c7895fab 100644 --- a/sql/agtype_coercions.sql +++ b/sql/agtype_coercions.sql @@ -173,3 +173,14 @@ AS 'MODULE_PATHNAME'; CREATE CAST (agtype AS json) WITH FUNCTION ag_catalog.agtype_to_json(agtype); + +CREATE FUNCTION ag_catalog.agtype_array_to_agtype(agtype[]) + RETURNS agtype + LANGUAGE c + IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (agtype[] AS agtype) + WITH FUNCTION ag_catalog.agtype_array_to_agtype(agtype[]); diff --git a/sql/agtype_typecast.sql b/sql/agtype_typecast.sql index aa551407..c29c0a65 100644 --- a/sql/agtype_typecast.sql +++ b/sql/agtype_typecast.sql @@ -181,8 +181,7 @@ CREATE FUNCTION ag_catalog.age_range(variadic "any") PARALLEL SAFE AS 'MODULE_PATHNAME'; -CREATE FUNCTION ag_catalog.age_unnest(agtype, - list_comprehension boolean = false) +CREATE FUNCTION ag_catalog.age_unnest(agtype) RETURNS SETOF agtype LANGUAGE c IMMUTABLE diff --git a/src/backend/nodes/ag_nodes.c b/src/backend/nodes/ag_nodes.c index e20670b2..9cab568d 100644 --- a/src/backend/nodes/ag_nodes.c +++ b/src/backend/nodes/ag_nodes.c @@ -48,6 +48,7 @@ const char *node_names[] = { "cypher_map_projection", "cypher_map_projection_element", "cypher_list", + "cypher_list_comprehension", "cypher_comparison_aexpr", "cypher_comparison_boolexpr", "cypher_string_match", @@ -115,6 +116,7 @@ const ExtensibleNodeMethods node_methods[] = { DEFINE_NODE_METHODS(cypher_map), DEFINE_NODE_METHODS(cypher_map_projection), DEFINE_NODE_METHODS(cypher_list), + DEFINE_NODE_METHODS(cypher_list_comprehension), DEFINE_NODE_METHODS(cypher_comparison_aexpr), DEFINE_NODE_METHODS(cypher_comparison_boolexpr), DEFINE_NODE_METHODS(cypher_string_match), diff --git a/src/backend/nodes/cypher_outfuncs.c b/src/backend/nodes/cypher_outfuncs.c index ea3d3f98..7aebbc31 100644 --- a/src/backend/nodes/cypher_outfuncs.c +++ b/src/backend/nodes/cypher_outfuncs.c @@ -116,7 +116,6 @@ void out_cypher_with(StringInfo str, const ExtensibleNode *node) DEFINE_AG_NODE(cypher_with); WRITE_BOOL_FIELD(distinct); - WRITE_BOOL_FIELD(subquery_intermediate); WRITE_NODE_FIELD(items); WRITE_NODE_FIELD(order_by); WRITE_NODE_FIELD(skip); @@ -175,9 +174,20 @@ void out_cypher_unwind(StringInfo str, const ExtensibleNode *node) DEFINE_AG_NODE(cypher_unwind); WRITE_NODE_FIELD(target); - WRITE_NODE_FIELD(collect); } +/* serialization function for the cypher_list_comprehension ExtensibleNode. */ +void out_cypher_list_comprehension(StringInfo str, const ExtensibleNode *node) +{ + DEFINE_AG_NODE(cypher_list_comprehension); + + WRITE_STRING_FIELD(varname); + WRITE_NODE_FIELD(expr); + WRITE_NODE_FIELD(where); + WRITE_NODE_FIELD(mapping_expr); +} + + /* serialization function for the cypher_delete ExtensibleNode. */ void out_cypher_merge(StringInfo str, const ExtensibleNode *node) { diff --git a/src/backend/optimizer/cypher_pathnode.c b/src/backend/optimizer/cypher_pathnode.c index a08cd30e..5e434425 100644 --- a/src/backend/optimizer/cypher_pathnode.c +++ b/src/backend/optimizer/cypher_pathnode.c @@ -23,6 +23,14 @@ #include "optimizer/cypher_createplan.h" #include "optimizer/cypher_pathnode.h" +#include "parser/cypher_analyze.h" +#include "executor/cypher_utils.h" +#include "optimizer/subselect.h" +#include "nodes/makefuncs.h" + +static Const *convert_sublink_to_subplan(PlannerInfo *root, + List *custom_private); +static bool expr_has_sublink(Node *node, void *context); const CustomPathMethods cypher_create_path_methods = { CREATE_PATH_NAME, plan_cypher_create_path, NULL}; @@ -183,10 +191,80 @@ CustomPath *create_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel, /* Make the original paths the children of the new path */ cp->custom_paths = rel->pathlist; - /* Store the metadata Delete will need in the execution phase. */ - cp->custom_private = custom_private; + + /* + * Store the metadata Merge will need in the execution phase. + * We may have a sublink here in case the user used a list + * comprehension in merge. + */ + if (rel->subroot->parse->hasSubLinks) + { + cp->custom_private = list_make1(convert_sublink_to_subplan(root, custom_private)); + } + else + { + cp->custom_private = custom_private; + } + /* Tells Postgres how to turn this path to the correct CustomScan */ cp->methods = &cypher_merge_path_methods; return cp; } + +/* + * Deserializes the merge information and checks if any property + * expression (prop_expr) contains a SubLink. + * If found, converts the SubLink to a SubPlan, updates the + * structure accordingly, and serializes it back. + */ +static Const *convert_sublink_to_subplan(PlannerInfo *root, List *custom_private) +{ + cypher_merge_information *merge_information; + char *serialized_data = NULL; + Const *c = NULL; + ListCell *lc = NULL; + StringInfo str = makeStringInfo(); + + c = linitial(custom_private); + serialized_data = (char *)c->constvalue; + merge_information = stringToNode(serialized_data); + + Assert(is_ag_node(merge_information, cypher_merge_information)); + + /* Only part where we can expect a sublink is in prop_expr. */ + foreach (lc, merge_information->path->target_nodes) + { + cypher_target_node *node = (cypher_target_node *)lfirst(lc); + Node *prop_expr = (Node *) node->prop_expr; + + if (expr_has_sublink(prop_expr, NULL)) + { + node->prop_expr = (Expr *) SS_process_sublinks(root, prop_expr, false); + } + } + + /* Serialize the information again and return it. */ + outNode(str, (Node *)merge_information); + + return makeConst(INTERNALOID, -1, InvalidOid, str->len, + PointerGetDatum(str->data), false, false); +} + +/* + * Helper function to check if the node has a sublink. + */ +static bool expr_has_sublink(Node *node, void *context) +{ + if (node == NULL) + { + return false; + } + + if (IsA(node, SubLink)) + { + return true; + } + + return cypher_expr_tree_walker(node, expr_has_sublink, context); +} diff --git a/src/backend/parser/cypher_analyze.c b/src/backend/parser/cypher_analyze.c index 6390b342..14f8547f 100644 --- a/src/backend/parser/cypher_analyze.c +++ b/src/backend/parser/cypher_analyze.c @@ -67,12 +67,6 @@ static Query *analyze_cypher_and_coerce(List *stmt, RangeTblFunction *rtfunc, const char *query_str, int query_loc, char *graph_name, uint32 graph_oid, Param *params); -cypher_clause *build_subquery_node(cypher_clause *next); - -/* expr tree walker */ -bool expr_contains_node(cypher_expression_condition is_expr, Node *expr); -bool expr_has_subquery(Node * expr); - void post_parse_analyze_init(void) { @@ -681,148 +675,40 @@ static int get_query_location(const int location, const char *source_str) return strchr(p + 1, '$') - source_str + 1; } + /* - * This is a specialized expression tree walker for finding exprs of a specified - * type. Create a function that checks for the type you want, and this function - * will iterate through the tree. + * This is an extension of postgres's raw_expression_tree_walker() function. + * It can walk cypher-specific nodes found in the expression tree during + * parse analysis. + * + * More nodes can be added to this function as needed. */ - -bool expr_contains_node(cypher_expression_condition is_expr, Node *expr) +bool cypher_raw_expr_tree_walker(Node *node, + bool (*walker)(), + void *context) { - if (!expr) - { - return false; - } - - switch (nodeTag(expr)) - { - case T_A_Const: - case T_ColumnRef: - case T_A_Indirection: - { - break; - } - case T_A_Expr: - { - A_Expr *a = (A_Expr *)expr; - - switch (a->kind) - { - case AEXPR_OP: - case AEXPR_IN: - { - if (expr_contains_node(is_expr, a->lexpr) || - expr_contains_node(is_expr, a->rexpr)) - { - return true; - } - break; - } - default: - ereport(ERROR, (errmsg_internal("unrecognized A_Expr kind: %d", - a->kind))); - } - break; - } - case T_BoolExpr: - { - BoolExpr *b = (BoolExpr *)expr; - ListCell *la; - - foreach(la, b->args) - { - Node *arg = lfirst(la); - - if (expr_contains_node(is_expr, arg)) - { - return true; - } - } - break; - } - case T_NullTest: - { - NullTest *n = (NullTest *)expr; - - if (expr_contains_node(is_expr, (Node *)n->arg)) - { - return true; - } - break; - } - case T_CaseExpr: - { - CaseExpr *cexpr = (CaseExpr *)expr; - ListCell *l; - - if (cexpr->arg && expr_contains_node(is_expr, (Node *) cexpr->arg)) - { - return true; - } - - foreach(l, cexpr->args) - { - CaseWhen *w = lfirst_node(CaseWhen, l); - Node *warg; - - warg = (Node *) w->expr; - - if (expr_contains_node(is_expr, warg)) - { - return true; - } - warg = (Node *)w->result; - - if (expr_contains_node(is_expr, warg)) - { - return true; - } - } - - if (expr_contains_node(is_expr , (Node *)cexpr->defresult)) - { - return true; - } + ListCell *temp; - break; - } - case T_CaseTestExpr: - { - break; - } - case T_CoalesceExpr: - { - CoalesceExpr *cexpr = (CoalesceExpr *) expr; - ListCell *args; + if (node == NULL) + return false; - foreach(args, cexpr->args) - { - Node *e = (Node *)lfirst(args); +#define WALK(n) walker((Node *) (n), context) +#define LIST_WALK(l) cypher_raw_expr_tree_walker((Node *) (l), walker, context) - if (expr_contains_node(is_expr, e)) - { - return true; - } - } - break; - } - case T_ExtensibleNode: + if (IsA(node, ExtensibleNode)) { - if (is_ag_node(expr, cypher_bool_const)) + if (is_ag_node(node, cypher_bool_const) || + is_ag_node(node, cypher_integer_const) || + is_ag_node(node, cypher_param) || + is_ag_node(node, cypher_sub_pattern) || + is_ag_node(node, cypher_sub_query)) + /* Add more non-recursible node types here as needed */ { - return is_expr(expr); - } - if (is_ag_node(expr, cypher_integer_const)) - { - return is_expr(expr); - } - if (is_ag_node(expr, cypher_param)) - { - return is_expr(expr); + return false; } - if (is_ag_node(expr, cypher_map)) + else if (is_ag_node(node, cypher_map)) { - cypher_map *cm = (cypher_map *)expr; + cypher_map *cm = (cypher_map *)node; ListCell *le; Assert(list_length(cm->keyvals) % 2 == 0); @@ -837,7 +723,7 @@ bool expr_contains_node(cypher_expression_condition is_expr, Node *expr) val = lfirst(le); - if (expr_contains_node(is_expr, val)) + if (WALK(val)) { return true; } @@ -845,216 +731,163 @@ bool expr_contains_node(cypher_expression_condition is_expr, Node *expr) le = lnext(cm->keyvals, le); } - break; } - if (is_ag_node(expr, cypher_map_projection)) + else if (is_ag_node(node, cypher_map_projection)) { - cypher_map_projection *cmp = (cypher_map_projection *)expr; - ListCell *lc; + cypher_map_projection *cmp = (cypher_map_projection *)node; - foreach(lc, cmp->map_elements) + if (LIST_WALK(cmp->map_elements)) { - cypher_map_projection_element *elem; - - elem = lfirst(lc); - - if (expr_contains_node(is_expr, elem->value)) - { - return true; - } + return true; } - - break; } - if (is_ag_node(expr, cypher_list)) + else if (is_ag_node(node, cypher_list)) { - cypher_list *cl = (cypher_list *)expr; - ListCell *le = NULL; - - foreach(le, cl->elems) + cypher_list *cl = (cypher_list *)node; + + if (LIST_WALK(cl->elems)) { - Node *texpr = lfirst(le); - - if (expr_contains_node(is_expr, texpr)) - { - return true; - } + return true; } - break; } - if (is_ag_node(expr, cypher_string_match)) + else if (is_ag_node(node, cypher_string_match)) { - cypher_string_match *csm = (cypher_string_match *)expr; + cypher_string_match *csm = (cypher_string_match *)node; - if (expr_contains_node(is_expr, csm->lhs) || - expr_contains_node(is_expr, csm->rhs)) + if (WALK(csm->lhs)) { return true; } - break; - } - if (is_ag_node(expr, cypher_typecast)) - { - cypher_typecast *t = (cypher_typecast *) expr; - if (expr_contains_node(is_expr, t->expr)) + if (WALK(csm->rhs)) { return true; } - break; } - if (is_ag_node(expr, cypher_comparison_aexpr)) + else if (is_ag_node(node, cypher_typecast)) { - cypher_comparison_aexpr *a = (cypher_comparison_aexpr *) expr; + cypher_typecast *t = (cypher_typecast *)node; - if (expr_contains_node(is_expr, a->lexpr) || - expr_contains_node(is_expr, a->rexpr)) + if (WALK(t->expr)) { return true; } - break; } - if (is_ag_node(expr, cypher_comparison_boolexpr)) + else if (is_ag_node(node, cypher_comparison_aexpr)) { - cypher_comparison_boolexpr *b = (cypher_comparison_boolexpr *) expr; - ListCell *la; + cypher_comparison_aexpr *a = (cypher_comparison_aexpr *)node; - foreach(la, b->args) + if (WALK(a->lexpr)) { - Node *arg = lfirst(la); + return true; + } - if (expr_contains_node(is_expr, arg)) - { - return true; - } + if (WALK(a->rexpr)) + { + return true; } - break; } - if (is_ag_node(expr, cypher_unwind)) + else if (is_ag_node(node, cypher_comparison_boolexpr)) { - cypher_unwind* lc = (cypher_unwind *)expr; - - if (expr_contains_node(is_expr, lc->where) || - expr_contains_node(is_expr, lc->collect)) + cypher_comparison_boolexpr *b = (cypher_comparison_boolexpr *)node; + + if (LIST_WALK(b->args)) { return true; } - break; } - - if (is_ag_node(expr, cypher_sub_pattern)) + else if (is_ag_node(node, cypher_unwind)) { - break; - } + cypher_unwind *unw = (cypher_unwind *)node; - if (is_ag_node(expr, cypher_sub_query)) - { - break; + if (WALK(unw->target)) + { + return true; + } } - ereport(ERROR, - (errmsg_internal("unrecognized ExtensibleNode: %s", - ((ExtensibleNode *)expr)->extnodename))); - - break; - } - case T_FuncCall: - { - FuncCall *fn = (FuncCall *)expr; - ListCell *arg; - - foreach(arg, fn->args) + else if (is_ag_node(node, cypher_list_comprehension)) { - Node *farg = NULL; + cypher_list_comprehension *lc = (cypher_list_comprehension *)node; - farg = (Node *)lfirst(arg); + if (WALK(lc->expr)) + { + return true; + } + + if (WALK(lc->where)) + { + return true; + } - if (expr_contains_node(is_expr, farg)) + if (WALK(lc->mapping_expr)) { return true; } } - break; + /* Add more node types here as needed */ + else + { + ereport(ERROR, + (errmsg_internal("unrecognized ExtensibleNode: %s", + ((ExtensibleNode *)node)->extnodename))); + } } - case T_SubLink: + /* + * postgres's raw expresssion tree walker does not handle List + */ + else if (IsA(node, List)) { - SubLink *s = (SubLink *)expr; - - if (expr_contains_node(is_expr, s->subselect)) + foreach(temp, (List *) node) { - return true; + if (WALK((Node *) lfirst(temp))) + return true; } - break; - } - default: - ereport(ERROR, (errmsg_internal("unrecognized node type: %d", - nodeTag(expr)))); } - return (is_expr(expr)); +#undef LIST_WALK + else + { + return raw_expression_tree_walker(node, walker, context); + } + + return false; } /* - * Function that checks if an expr is a cypher_sub_query. Used in tandem with - * expr_contains_node. Can write more similar to this to find similar nodes. + * This is an extension of postgres's expression_tree_walker() function. + * It is meant to walk cypher-specific nodes found in the expression tree + * post parse analysis. + * + * More nodes can be added to this function as needed. */ - -bool expr_has_subquery(Node * expr) +bool cypher_expr_tree_walker(Node *node, + bool (*walker)(), + void *context) { - if (expr == NULL) + if (node == NULL) { return false; } - if (IsA(expr, ExtensibleNode)) - { - if (is_ag_node(expr, cypher_sub_query)) - { - return true; - } - } - return false; -} +#define LIST_WALK(l) cypher_expr_tree_walker((Node *) (l), walker, context) -/* - * This function constructs an intermediate WITH node for processing subqueries - */ -cypher_clause *build_subquery_node(cypher_clause *next) -{ - cypher_match *match = (cypher_match *)next->self; - cypher_clause *where_container_clause; - cypher_with *with_clause = make_ag_node(cypher_with); - ColumnRef *cr; - ResTarget *rt; - - /* construct the column ref star */ - cr = makeNode(ColumnRef); - cr->fields = list_make1(makeNode(A_Star)); - cr->location = exprLocation((Node *)next); - - /*construct the restarget */ - rt = makeNode(ResTarget); - rt->name = NULL; - rt->indirection = NIL; - rt->val = (Node *)cr; - rt->location = exprLocation((Node *)next); - - - /* construct the with_clause */ - with_clause->items = list_make1(rt); - with_clause->where = match->where; - with_clause->subquery_intermediate = true; + if (IsA(node, ExtensibleNode)) + { + /* Add our nodes that can appear post parsing stage */ - /* - * create the where container, and set the match (next) as the - * prev of the where container - */ - where_container_clause = palloc(sizeof(*where_container_clause)); - where_container_clause->self = (Node *)with_clause; - where_container_clause->next = NULL; - where_container_clause->prev = next; + ereport(ERROR, + (errmsg_internal("unrecognized ExtensibleNode: %s", + ((ExtensibleNode *)node)->extnodename))); + } +#undef WALK +#undef LIST_WALK + else + { + return expression_tree_walker(node, walker, context); + } - return where_container_clause; + return false; } static Query *analyze_cypher(List *stmt, ParseState *parent_pstate, @@ -1083,23 +916,6 @@ static Query *analyze_cypher(List *stmt, ParseState *parent_pstate, next->self = lfirst(lc); next->prev = clause; - /* check for subqueries in match */ - if (is_ag_node(next->self, cypher_match)) - { - cypher_match *match = (cypher_match *)next->self; - - if (match->where != NULL && expr_contains_node(expr_has_subquery, match->where)) - { - /* advance the clause iterator to the intermediate clause position */ - clause = build_subquery_node(next); - - /* set the next of the match to the where_container_clause */ - match->where = NULL; - next->next = clause; - continue; - } - } - if (clause != NULL) { clause->next = next; diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 2495042a..22c5b823 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -248,6 +248,11 @@ static void get_res_cols(ParseState *pstate, ParseNamespaceItem *l_pnsi, /* unwind */ static Query *transform_cypher_unwind(cypher_parsestate *cpstate, cypher_clause *clause); + +/* list comprehension */ +static Query *transform_cypher_list_comprehension(cypher_parsestate *cpstate, + cypher_clause *clause); + /* merge */ static Query *transform_cypher_merge(cypher_parsestate *cpstate, cypher_clause *clause); @@ -290,7 +295,7 @@ static Query *transform_cypher_call_subquery(cypher_parsestate *cpstate, #define transform_prev_cypher_clause(cpstate, prev_clause, add_rte_to_query) \ transform_cypher_clause_as_subquery(cpstate, transform_cypher_clause, \ prev_clause, NULL, add_rte_to_query) -ParseNamespaceItem +static ParseNamespaceItem *transform_cypher_clause_as_subquery(cypher_parsestate *cpstate, transform_method transform, cypher_clause *clause, @@ -326,9 +331,9 @@ static FuncExpr *make_clause_func_expr(char *function_name, /* for VLE support */ static ParseNamespaceItem *transform_RangeFunction(cypher_parsestate *cpstate, RangeFunction *r); -static Node *transform_VLE_Function(cypher_parsestate *cpstate, Node *n, - RangeTblEntry **top_rte, int *top_rti, - List **namespace); +static Node *transform_from_clause_item(cypher_parsestate *cpstate, Node *n, + RangeTblEntry **top_rte, int *top_rti, + List **namespace); static ParseNamespaceItem *append_VLE_Func_to_FromClause(cypher_parsestate *cpstate, Node *n); static void setNamespaceLateralState(List *namespace, bool lateral_only, @@ -336,6 +341,7 @@ static void setNamespaceLateralState(List *namespace, bool lateral_only, static bool isa_special_VLE_case(cypher_path *path); static ParseNamespaceItem *find_pnsi(cypher_parsestate *cpstate, char *varname); +static bool has_list_comp_or_subquery(Node *expr, void *context); /* * transform a cypher_clause @@ -398,19 +404,16 @@ Query *transform_cypher_clause(cypher_parsestate *cpstate, } else if (is_ag_node(self, cypher_unwind)) { - cypher_unwind *n = (cypher_unwind *) self; - if (n->collect != NULL) - { - cpstate->p_list_comp = true; - } - result = transform_cypher_clause_with_where(cpstate, - transform_cypher_unwind, - clause, n->where); + result = transform_cypher_unwind(cpstate, clause); } else if (is_ag_node(self, cypher_call)) { result = transform_cypher_call_stmt(cpstate, clause); } + else if (is_ag_node(self, cypher_list_comprehension)) + { + result = transform_cypher_list_comprehension(cpstate, clause); + } else { ereport(ERROR, (errmsg_internal("unexpected Node for cypher_clause"))); @@ -446,23 +449,6 @@ static cypher_clause *make_cypher_clause(List *stmt) next->self = lfirst(lc); next->prev = clause; - /* check for subqueries in match */ - if (is_ag_node(next->self, cypher_match)) - { - cypher_match *match = (cypher_match *)next->self; - - if (match->where != NULL && expr_contains_node(expr_has_subquery, match->where)) - { - /* advance the clause iterator to the intermediate clause position */ - clause = build_subquery_node(next); - - /* set the next of the match to the where_container_clause */ - match->where = NULL; - next->next = clause; - continue; - } - } - if (clause != NULL) { clause->next = next; @@ -660,6 +646,7 @@ static Query *transform_cypher_union(cypher_parsestate *cpstate, qry->rtable = pstate->p_rtable; qry->jointree = makeFromExpr(pstate->p_joinlist, NULL); qry->hasAggs = pstate->p_hasAggs; + qry->hasSubLinks = pstate->p_hasSubLinks; assign_query_collations(pstate, qry); @@ -1259,6 +1246,7 @@ static Query *transform_cypher_call_subquery(cypher_parsestate *cpstate, query->rtable = cpstate->pstate.p_rtable; query->jointree = makeFromExpr(cpstate->pstate.p_joinlist, (Node *)where_qual); query->hasAggs = pstate->p_hasAggs; + query->hasSubLinks = pstate->p_hasSubLinks; assign_query_collations(pstate, query); @@ -1330,6 +1318,8 @@ static Query *transform_cypher_delete(cypher_parsestate *cpstate, query->rtable = pstate->p_rtable; query->jointree = makeFromExpr(pstate->p_joinlist, NULL); + query->hasAggs = pstate->p_hasAggs; + query->hasSubLinks = pstate->p_hasSubLinks; return query; } @@ -1353,9 +1343,6 @@ static Query *transform_cypher_unwind(cypher_parsestate *cpstate, Node *funcexpr; TargetEntry *te; ParseNamespaceItem *pnsi; - bool is_list_comp = self->collect != NULL; - bool has_agg = - is_list_comp || has_a_cypher_list_comprehension_node(self->target->val); query = makeNode(Query); query->commandType = CMD_SELECT; @@ -1384,13 +1371,14 @@ static Query *transform_cypher_unwind(cypher_parsestate *cpstate, ereport(ERROR, (errcode(ERRCODE_DUPLICATE_ALIAS), errmsg("duplicate variable \"%s\"", self->target->name), - parser_errposition(pstate, target_syntax_loc))); + parser_errposition((ParseState *) cpstate, + target_syntax_loc))); } expr = transform_cypher_expr(cpstate, self->target->val, EXPR_KIND_SELECT_TARGET); - if (!has_agg && nodeTag(expr) == T_Aggref) + if (nodeTag(expr) == T_Aggref) { ereport(ERROR, errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Invalid use of aggregation in this context"), @@ -1403,12 +1391,11 @@ static Query *transform_cypher_unwind(cypher_parsestate *cpstate, old_expr_kind = pstate->p_expr_kind; pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET; funcexpr = ParseFuncOrColumn(pstate, unwind->funcname, - list_make2(expr, makeBoolConst(is_list_comp, false)), + list_make1(expr), pstate->p_last_srf, unwind, false, target_syntax_loc); pstate->p_expr_kind = old_expr_kind; - pstate->p_hasAggs = has_agg; te = makeTargetEntry((Expr *) funcexpr, (AttrNumber) pstate->p_next_resno++, @@ -1419,12 +1406,94 @@ static Query *transform_cypher_unwind(cypher_parsestate *cpstate, query->jointree = makeFromExpr(pstate->p_joinlist, NULL); query->hasTargetSRFs = pstate->p_hasTargetSRFs; query->hasAggs = pstate->p_hasAggs; + query->hasSubLinks = pstate->p_hasSubLinks; assign_query_collations(pstate, query); return query; } +/* + * [i IN u WHERE i<2 | i^2] + * + * | | | | | + * \|/ \|/ \|/ \|/ \|/ + * + * SELECT i^2 FROM age_unnest(u) AS i WHERE i>2; + */ +static Query *transform_cypher_list_comprehension(cypher_parsestate *cpstate, + cypher_clause *clause) +{ + Query *query; + RangeFunction *rf; + cypher_list_comprehension *list_comp = (cypher_list_comprehension *) clause->self; + FuncCall *func_call; + Node *return_expr, *qual, *n; + RangeTblEntry *rte = NULL; + int rtindex; + List *namespace = NULL; + TargetEntry *te; + cypher_parsestate *child_cpstate = make_cypher_parsestate(cpstate); + ParseState *child_pstate = (ParseState *) child_cpstate; + + query = makeNode(Query); + query->commandType = CMD_SELECT; + + func_call = makeFuncCall(list_make1(makeString("unnest")), + list_make1(list_comp->expr), + COERCE_SQL_SYNTAX, -1); + + rf = makeNode(RangeFunction); + rf->lateral = false; + rf->ordinality = false; + rf->is_rowsfrom = false; + rf->functions = list_make1(list_make2((Node *) func_call, NIL)); + rf->alias = makeAlias(list_comp->varname, NIL); + rf->coldeflist = NIL; + + n = transform_from_clause_item(child_cpstate, (Node *) rf, + &rte, &rtindex, &namespace); + checkNameSpaceConflicts(child_pstate, child_pstate->p_namespace, namespace); + child_pstate->p_joinlist = lappend(child_pstate->p_joinlist, n); + child_pstate->p_namespace = list_concat(child_pstate->p_namespace, namespace); + + /* make all namespace items unconditionally visible */ + setNamespaceLateralState(child_pstate->p_namespace, false, true); + + return_expr = transform_cypher_expr(child_cpstate, list_comp->mapping_expr, + EXPR_KIND_SELECT_TARGET); + + te = makeTargetEntry((Expr *) return_expr, + (AttrNumber) child_pstate->p_next_resno++, + list_comp->varname, false); + + qual = transform_cypher_expr(child_cpstate, list_comp->where, + EXPR_KIND_WHERE); + if (qual) + { + qual = coerce_to_boolean(child_pstate, qual, "WHERE"); + } + + query->targetList = lappend(query->targetList, te); + query->jointree = makeFromExpr(child_pstate->p_joinlist, (Node *) qual); + query->rtable = child_pstate->p_rtable; + query->hasAggs = child_pstate->p_hasAggs; + query->hasSubLinks = child_pstate->p_hasSubLinks; + query->hasTargetSRFs = child_pstate->p_hasTargetSRFs; + + assign_query_collations(child_pstate, query); + + if (child_pstate->p_hasAggs || + query->groupClause || query->groupingSets || query->havingQual) + { + parse_check_aggregates(child_pstate, query); + } + + free_cypher_parsestate(child_cpstate); + + return query; +} + /* * Iterate through the list of items to delete and extract the variable name. * Then find the resno that the variable name belongs to. @@ -1560,6 +1629,8 @@ static Query *transform_cypher_set(cypher_parsestate *cpstate, query->rtable = pstate->p_rtable; query->jointree = makeFromExpr(pstate->p_joinlist, NULL); + query->hasAggs = pstate->p_hasAggs; + query->hasSubLinks = pstate->p_hasSubLinks; return query; } @@ -1833,12 +1904,7 @@ cypher_update_information *transform_cypher_set_item_list( EXPR_KIND_SELECT_TARGET, NULL, false); - if (has_a_cypher_list_comprehension_node(set_item->expr)) - { - query->hasAggs = true; - } - - if (!query->hasAggs && nodeTag(target_item->expr) == T_Aggref) + if (nodeTag(target_item->expr) == T_Aggref) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Invalid use of aggregation in this context"), @@ -2169,6 +2235,7 @@ static Query *transform_cypher_return(cypher_parsestate *cpstate, query->rtable = pstate->p_rtable; query->jointree = makeFromExpr(pstate->p_joinlist, NULL); query->hasAggs = pstate->p_hasAggs; + query->hasSubLinks = pstate->p_hasSubLinks; assign_query_collations(pstate, query); @@ -2357,7 +2424,6 @@ static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate, { ParseState *pstate = (ParseState *)cpstate; Query *query; - Node *self = clause->self; Node *where_qual = NULL; if (where) @@ -2370,10 +2436,8 @@ static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate, pnsi = transform_cypher_clause_as_subquery(cpstate, transform, clause, NULL, true); - Assert(pnsi != NULL); rtindex = list_length(pstate->p_rtable); - /* rte is the only RangeTblEntry in pstate */ if (rtindex != 1) { @@ -2388,78 +2452,14 @@ static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate, * next clause */ query->targetList = expandNSItemAttrs(pstate, pnsi, 0, true, -1); - markTargetListOrigins(pstate, query->targetList); - query->rtable = pstate->p_rtable; where_qual = transform_cypher_expr(cpstate, where, EXPR_KIND_WHERE); - where_qual = coerce_to_boolean(pstate, where_qual, "WHERE"); - /* check if we have a list comprehension in the where clause */ - if (cpstate->p_list_comp && - has_a_cypher_list_comprehension_node(where)) - { - List *groupClause = NIL; - ListCell *li; - bool has_a_star; - - has_a_star = false; - query->jointree = makeFromExpr(pstate->p_joinlist, NULL); - query->havingQual = where_qual; - - foreach (li, ((cypher_return *)self)->items) - { - ResTarget *item = lfirst(li); - ColumnRef *cref; - - /* - * We need to handle the case where the item is a A_star. In this - * case we will need to build group by using targetList. - */ - if (IsA(item->val, ColumnRef)) - { - cref = (ColumnRef *)item->val; - if (IsA(linitial(cref->fields), A_Star)) - { - has_a_star = true; - continue; - } - } - - groupClause = lappend(groupClause, item->val); - } - - /* - * If there is A_star flag, build the group by clause - * using the targetList. - */ - if (has_a_star) - { - ListCell *lc; - foreach (lc, query->targetList) - { - TargetEntry *te = lfirst(lc); - ColumnRef *cref = makeNode(ColumnRef); - - cref->fields = list_make1(makeString(te->resname)); - cref->location = exprLocation((Node *)te->expr); - - groupClause = lappend(groupClause, cref); - } - } - query->groupClause = transform_group_clause(cpstate, groupClause, - &query->groupingSets, - &query->targetList, - query->sortClause, - EXPR_KIND_GROUP_BY); - - } - else - { - query->jointree = makeFromExpr(pstate->p_joinlist, where_qual); - } + query->jointree = makeFromExpr(pstate->p_joinlist, where_qual); + assign_query_collations(pstate, query); } else { @@ -2470,8 +2470,6 @@ static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate, query->hasTargetSRFs = pstate->p_hasTargetSRFs; query->hasAggs = pstate->p_hasAggs; - assign_query_collations(pstate, query); - return query; } @@ -2479,6 +2477,7 @@ static Query *transform_cypher_match(cypher_parsestate *cpstate, cypher_clause *clause) { cypher_match *match_self = (cypher_match*) clause->self; + Node *where = match_self->where; if(!match_check_valid_label(match_self, cpstate)) { @@ -2496,9 +2495,39 @@ static Query *transform_cypher_match(cypher_parsestate *cpstate, (Node *)r, -1); } + if (has_list_comp_or_subquery((Node *)match_self->where, NULL)) + { + match_self->where = NULL; + return transform_cypher_clause_with_where(cpstate, + transform_cypher_match_pattern, clause, where); + } + return transform_cypher_match_pattern(cpstate, clause); } +/* + * Function that checks if an expr has a cypher_sub_query or + * cypher_list_comprehension. + */ +static bool has_list_comp_or_subquery(Node *expr, void *context) +{ + if (expr == NULL) + { + return false; + } + + if (IsA(expr, ExtensibleNode)) + { + if (is_ag_node(expr, cypher_sub_query) || + is_ag_node(expr, cypher_list_comprehension)) + { + return true; + } + } + + return cypher_raw_expr_tree_walker(expr, has_list_comp_or_subquery, context); +} + /* * Transform the clause into a subquery. This subquery will be used * in a join so setup the namespace item and the created the rtr @@ -2679,18 +2708,6 @@ static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate, { cypher_clause *next = clause->next; - /* - * check if optional match has a subquery node-- it could still - * be following a match - */ - if(is_ag_node(next->self, cypher_with)) - { - cypher_with *next_with = (cypher_with *)next->self; - if (next_with->subquery_intermediate == true) - { - next = next->next; - } - } if (is_ag_node(next->self, cypher_match)) { cypher_match *next_self = (cypher_match *)next->self; @@ -2950,9 +2967,9 @@ static Query *transform_cypher_sub_query(cypher_parsestate *cpstate, * will transform the VLE function, depending on type. Currently, only * RangeFunctions are supported. But, others may be in the future. */ -static Node *transform_VLE_Function(cypher_parsestate *cpstate, Node *n, - RangeTblEntry **top_rte, int *top_rti, - List **namespace) +static Node *transform_from_clause_item(cypher_parsestate *cpstate, Node *n, + RangeTblEntry **top_rte, int *top_rti, + List **namespace) { ParseState *pstate = &cpstate->pstate; @@ -3020,7 +3037,7 @@ static ParseNamespaceItem *append_VLE_Func_to_FromClause(cypher_parsestate *cpst * Following PG's FROM clause logic, just in case we need to expand it in * the future, we process the items in another function. */ - n = transform_VLE_Function(cpstate, n, &rte, &rtindex, &namespace); + n = transform_from_clause_item(cpstate, n, &rte, &rtindex, &namespace); /* this should not happen */ Assert(n != NULL); @@ -3212,36 +3229,7 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, } query->rtable = cpstate->pstate.p_rtable; - - if (cpstate->p_list_comp) - { - List *groupList = NIL; - - query->jointree = makeFromExpr(cpstate->pstate.p_joinlist, NULL); - query->havingQual = (Node *)expr; - - foreach (lc, query->targetList) - { - TargetEntry *te = lfirst(lc); - ColumnRef *cref = makeNode(ColumnRef); - - cref->fields = list_make1(makeString(te->resname)); - cref->location = exprLocation((Node *)te->expr); - - groupList = lappend(groupList, cref); - } - - query->groupClause = transform_group_clause(cpstate, groupList, - &query->groupingSets, - &query->targetList, - query->sortClause, - EXPR_KIND_GROUP_BY); - } - else - { - query->jointree = makeFromExpr(cpstate->pstate.p_joinlist, - (Node *)expr); - } + query->jointree = makeFromExpr(cpstate->pstate.p_joinlist, (Node *)expr); } /* @@ -5610,6 +5598,7 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, query->rtable = pstate->p_rtable; query->jointree = makeFromExpr(pstate->p_joinlist, NULL); query->hasAggs = pstate->p_hasAggs; + query->hasSubLinks = pstate->p_hasSubLinks; return query; } @@ -6245,7 +6234,7 @@ static Expr *cypher_create_properties(cypher_parsestate *cpstate, * This function is similar to transformFromClause() that is called with a * single RangeSubselect. */ -ParseNamespaceItem * +static ParseNamespaceItem * transform_cypher_clause_as_subquery(cypher_parsestate *cpstate, transform_method transform, cypher_clause *clause, @@ -6267,8 +6256,7 @@ transform_cypher_clause_as_subquery(cypher_parsestate *cpstate, pstate->p_expr_kind == EXPR_KIND_OTHER || pstate->p_expr_kind == EXPR_KIND_WHERE || pstate->p_expr_kind == EXPR_KIND_SELECT_TARGET || - pstate->p_expr_kind == EXPR_KIND_FROM_SUBSELECT || - pstate->p_expr_kind == EXPR_KIND_INSERT_TARGET); + pstate->p_expr_kind == EXPR_KIND_FROM_SUBSELECT); /* * As these are all sub queries, if this is just of type NONE, note it as a @@ -6472,6 +6460,12 @@ Query *cypher_parse_sub_analyze(Node *parseTree, cypher_clause *clause; Query *query; + if (IsA(parseTree, Query)) + { + /* Already transformed, just return it */ + return (Query *)parseTree; + } + pstate->p_parent_cte = parentCTE; pstate->p_locked_from_parent = locked_from_parent; pstate->p_resolve_unknowns = resolve_unknowns; @@ -6621,7 +6615,6 @@ static Query *transform_cypher_merge(cypher_parsestate *cpstate, query->rtable = pstate->p_rtable; query->jointree = makeFromExpr(pstate->p_joinlist, NULL); query->hasAggs = pstate->p_hasAggs; - query->hasSubLinks = pstate->p_hasSubLinks; assign_query_collations(pstate, query); diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index 8afc0c41..5b3ab319 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -98,8 +98,6 @@ static Node *transform_WholeRowRef(ParseState *pstate, ParseNamespaceItem *pnsi, static ArrayExpr *make_agtype_array_expr(List *args); static Node *transform_column_ref_for_indirection(cypher_parsestate *cpstate, ColumnRef *cr); -static Node *transform_cypher_list_comprehension(cypher_parsestate *cpstate, - cypher_unwind *expr); static Node *transform_external_ext_FuncCall(cypher_parsestate *cpstate, FuncCall *fn, List *targs, Form_pg_proc procform, @@ -242,12 +240,6 @@ static Node *transform_cypher_expr_recurse(cypher_parsestate *cpstate, return transform_cypher_comparison_boolexpr(cpstate, (cypher_comparison_boolexpr *)expr); } - if (is_ag_node(expr, cypher_unwind)) - { - return transform_cypher_list_comprehension(cpstate, - (cypher_unwind *) expr); - } - ereport(ERROR, (errmsg_internal("unrecognized ExtensibleNode: %s", ((ExtensibleNode *)expr)->extnodename))); @@ -258,7 +250,9 @@ static Node *transform_cypher_expr_recurse(cypher_parsestate *cpstate, return transform_FuncCall(cpstate, (FuncCall *)expr); case T_SubLink: return transform_SubLink(cpstate, (SubLink *)expr); - break; + case T_Const: + /* Already transformed */ + return expr; default: ereport(ERROR, (errmsg_internal("unrecognized node type: %d", nodeTag(expr)))); @@ -390,26 +384,8 @@ static Node *transform_ColumnRef(cypher_parsestate *cpstate, ColumnRef *cref) Assert(IsA(field1, String)); colname = strVal(field1); - if (cpstate->p_list_comp && - (pstate->p_expr_kind == EXPR_KIND_WHERE || - pstate->p_expr_kind == EXPR_KIND_SELECT_TARGET) && - list_length(pstate->p_namespace) > 0) - { - /* - * Just scan through the last pnsi(that is for list comp) - * to find the column. - */ - node = scanNSItemForColumn(pstate, - llast(pstate->p_namespace), - 0, colname, cref->location); - } - else - { - /* Try to identify as an unqualified column */ - node = colNameToVar(pstate, colname, false, - cref->location); - } - + /* Try to identify as an unqualified column */ + node = colNameToVar(pstate, colname, false, cref->location); if (node != NULL) { break; @@ -1321,7 +1297,7 @@ static Node *transform_column_ref_for_indirection(cypher_parsestate *cpstate, } /* find the properties column of the NSI and return a var for it */ - node = scanNSItemForColumn(pstate, pnsi, levels_up, "properties", + node = scanNSItemForColumn(pstate, pnsi, levels_up, "properties", cr->location); /* @@ -2372,6 +2348,7 @@ static Node *transform_SubLink(cypher_parsestate *cpstate, SubLink *sublink) case EXPR_KIND_SELECT_TARGET: case EXPR_KIND_FROM_SUBSELECT: case EXPR_KIND_WHERE: + case EXPR_KIND_INSERT_TARGET: /* okay */ break; default: @@ -2411,7 +2388,7 @@ static Node *transform_SubLink(cypher_parsestate *cpstate, SubLink *sublink) sublink->operName = NIL; } else if (sublink->subLinkType == EXPR_SUBLINK || - sublink->subLinkType == ARRAY_SUBLINK) + sublink->subLinkType == ARRAY_SUBLINK) { /* * Make sure the subselect delivers a single column (ignoring resjunk @@ -2443,31 +2420,3 @@ static Node *transform_SubLink(cypher_parsestate *cpstate, SubLink *sublink) return result; } - -static Node *transform_cypher_list_comprehension(cypher_parsestate *cpstate, - cypher_unwind *unwind) -{ - cypher_clause cc; - Node* expr; - ParseNamespaceItem *pnsi; - ParseState *pstate = (ParseState *)cpstate; - - cpstate->p_list_comp = true; - pstate->p_lateral_active = true; - - cc.prev = NULL; - cc.next = NULL; - cc.self = (Node *)unwind; - - pnsi = transform_cypher_clause_as_subquery(cpstate, - transform_cypher_clause, - &cc, NULL, true); - - expr = transform_cypher_expr(cpstate, unwind->collect, - EXPR_KIND_SELECT_TARGET); - - pnsi->p_cols_visible = false; - pstate->p_lateral_active = false; - - return expr; -} diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y index 31e062a5..31c07a5b 100644 --- a/src/backend/parser/cypher_gram.y +++ b/src/backend/parser/cypher_gram.y @@ -21,6 +21,7 @@ #include "postgres.h" #include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" #include "parser/parser.h" #include "parser/cypher_gram.h" @@ -259,15 +260,9 @@ static Node *build_comparison_expression(Node *left_grammar_node, char *opr_name, int location); /* list_comprehension */ -static Node *verify_rule_as_list_comprehension(Node *expr, Node *expr2, - Node *where, Node *mapping_expr, - int var_loc, int expr_loc, - int where_loc, int mapping_loc); - -static Node *build_list_comprehension_node(ColumnRef *var_name, Node *expr, +static Node *build_list_comprehension_node(Node *var, Node *expr, Node *where, Node *mapping_expr, - int var_loc, int expr_loc, - int where_loc,int mapping_loc); + int location); %} %% @@ -1078,8 +1073,6 @@ unwind: n = make_ag_node(cypher_unwind); n->target = res; - n->where = NULL; - n->collect = NULL; $$ = (Node *) n; } @@ -2083,6 +2076,7 @@ expr_literal: | map | map_projection | list + | list_comprehension ; map: @@ -2202,7 +2196,6 @@ list: $$ = (Node *)n; } - | list_comprehension ; /* @@ -2213,40 +2206,28 @@ list: list_comprehension: '[' expr IN expr ']' { - Node *n = $2; - Node *result = NULL; - /* - * If the first expr is a ColumnRef(variable), then the rule - * should evaluate as a list comprehension. Otherwise, it should - * evaluate as an IN operator. + * If the first expr is not a ColumnRef(variable), then the rule + * should evaluate as an IN operator. */ - if (nodeTag(n) == T_ColumnRef) + if (!IsA($2, ColumnRef)) { - ColumnRef *cref = (ColumnRef *)n; - result = build_list_comprehension_node(cref, $4, NULL, NULL, - @2, @4, 0, 0); + $$ = (Node *)makeSimpleA_Expr(AEXPR_IN, "=", $2, $4, @3); } - else - { - result = (Node *)makeSimpleA_Expr(AEXPR_IN, "=", n, $4, @3); - } - $$ = result; + + $$ = build_list_comprehension_node($2, $4, NULL, NULL, @1); } | '[' expr IN expr WHERE expr ']' { - $$ = verify_rule_as_list_comprehension($2, $4, $6, NULL, - @2, @4, @6, 0); + $$ = build_list_comprehension_node($2, $4, $6, NULL, @1); } | '[' expr IN expr '|' expr ']' { - $$ = verify_rule_as_list_comprehension($2, $4, NULL, $6, - @2, @4, 0, @6); + $$ = build_list_comprehension_node($2, $4, NULL, $6, @1); } | '[' expr IN expr WHERE expr '|' expr ']' { - $$ = verify_rule_as_list_comprehension($2, $4, $6, $8, - @2, @4, @6, @8); + $$ = build_list_comprehension_node($2, $4, $6, $8, @1); } ; @@ -2897,6 +2878,10 @@ static FuncCall *node_to_agtype(Node * fnode, char *type, int location) { funcname = lappend(funcname, makeString("bool_to_agtype")); } + else if (pg_strcasecmp(type, "agtype[]") == 0) + { + funcname = lappend(funcname, makeString("agtype_array_to_agtype")); + } else { ereport(ERROR, @@ -3277,90 +3262,51 @@ static cypher_relationship *build_VLE_relation(List *left_arg, return cr; } -/* Helper function to verify that the rule is a list comprehension */ -static Node *verify_rule_as_list_comprehension(Node *expr, Node *expr2, - Node *where, Node *mapping_expr, - int var_loc, int expr_loc, - int where_loc, int mapping_loc) +/* helper function to build a list_comprehension grammar node */ +static Node *build_list_comprehension_node(Node *var, Node *expr, + Node *where, Node *mapping_expr, + int location) { - Node *result = NULL; + SubLink *sub; + String *val; + ColumnRef *cref = NULL; + cypher_list_comprehension *list_comp = NULL; - /* - * If the first expression is a ColumnRef, then we can build a - * list_comprehension node. - * Else its an invalid use of IN operator. - */ - if (nodeTag(expr) == T_ColumnRef) - { - ColumnRef *cref = (ColumnRef *)expr; - result = build_list_comprehension_node(cref, expr2, where, - mapping_expr, var_loc, - expr_loc, where_loc, - mapping_loc); - } - else + if (!IsA(var, ColumnRef)) { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Syntax error at or near IN"))); } - return result; -} -/* helper function to build a list_comprehension grammar node */ -static Node *build_list_comprehension_node(ColumnRef *cref, Node *expr, - Node *where, Node *mapping_expr, - int var_loc, int expr_loc, - int where_loc, int mapping_loc) -{ - ResTarget *res = NULL; - cypher_unwind *unwind = NULL; - char *var_name = NULL; - String *val; - - /* Extract name from cref */ + cref = (ColumnRef *)var; val = linitial(cref->fields); - if (!IsA(val, String)) { ereport(ERROR, - (errmsg_internal("unexpected Node for cypher_clause"))); + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("Invalid list comprehension variable name"))); } - var_name = val->sval; + /* build the list comprehension node */ + list_comp = make_ag_node(cypher_list_comprehension); + list_comp->varname = val->sval; + list_comp->expr = expr; + list_comp->where = where; + list_comp->mapping_expr = (mapping_expr != NULL) ? mapping_expr : + (Node *) cref; /* - * Build the ResTarget node for the UNWIND variable var_name attached to - * expr. + * Build an ARRAY sublink and attach list_comp as sub-select, + * it will be transformed in to query tree by us and reattached for + * pg to process. */ - res = makeNode(ResTarget); - res->name = var_name; - res->val = (Node *)expr; - res->location = expr_loc; - - /* build the UNWIND node */ - unwind = make_ag_node(cypher_unwind); - unwind->target = res; - unwind->where = where; - - /* if there is a mapping function, add its arg to collect */ - if (mapping_expr != NULL) - { - unwind->collect = make_function_expr(list_make1(makeString("collect")), - list_make1(mapping_expr), - mapping_loc); - } - /* - * Otherwise, we need to add in the ColumnRef of the variable var_name as - * the arg to collect instead. This implies that the RETURN variable is - * var_name. - */ - else - { - unwind->collect = make_function_expr(list_make1(makeString("collect")), - list_make1(cref), mapping_loc); - } - - /* return the UNWIND node */ - return (Node *)unwind; + sub = makeNode(SubLink); + sub->subLinkType = ARRAY_SUBLINK; + sub->subLinkId = 0; + sub->testexpr = NULL; + sub->subselect = (Node *)list_comp; + sub->location = location; + + return (Node *) node_to_agtype((Node *)sub, "agtype[]", location); } diff --git a/src/backend/parser/cypher_item.c b/src/backend/parser/cypher_item.c index 81413fa3..e9fb046e 100644 --- a/src/backend/parser/cypher_item.c +++ b/src/backend/parser/cypher_item.c @@ -27,16 +27,13 @@ #include "nodes/makefuncs.h" #include "parser/parse_relation.h" #include "parser/parse_target.h" -#include "miscadmin.h" #include "parser/cypher_expr.h" #include "parser/cypher_item.h" -#include "parser/cypher_clause.h" static List *ExpandAllTables(ParseState *pstate, int location); static List *expand_rel_attrs(ParseState *pstate, RangeTblEntry *rte, int rtindex, int sublevels_up, int location); -bool has_a_cypher_list_comprehension_node(Node *expr); /* see transformTargetEntry() */ TargetEntry *transform_cypher_item(cypher_parsestate *cpstate, Node *node, @@ -44,17 +41,10 @@ TargetEntry *transform_cypher_item(cypher_parsestate *cpstate, Node *node, char *colname, bool resjunk) { ParseState *pstate = (ParseState *)cpstate; - bool old_p_lateral_active = pstate->p_lateral_active; - - /* we want to see lateral variables */ - pstate->p_lateral_active = true; if (!expr) expr = transform_cypher_expr(cpstate, node, expr_kind); - /* set lateral back to what it was */ - pstate->p_lateral_active = old_p_lateral_active; - if (!colname && !resjunk) colname = FigureColname(node); @@ -62,143 +52,6 @@ TargetEntry *transform_cypher_item(cypher_parsestate *cpstate, Node *node, colname, resjunk); } -/* - * Helper function to determine if the passed node has a list_comprehension - * node embedded in it. - */ -bool has_a_cypher_list_comprehension_node(Node *expr) -{ - /* return false on NULL input */ - if (expr == NULL) - { - return false; - } - - /* since this function recurses, it could be driven to stack overflow */ - check_stack_depth(); - - switch (nodeTag(expr)) - { - case T_A_Expr: - { - /* - * We need to recurse into the left and right nodes - * to check if there is an unwind node in there - */ - A_Expr *a_expr = (A_Expr *)expr; - - return (has_a_cypher_list_comprehension_node(a_expr->lexpr) || - has_a_cypher_list_comprehension_node(a_expr->rexpr)); - } - case T_BoolExpr: - { - BoolExpr *bexpr = (BoolExpr *)expr; - ListCell *lc; - - /* is any of the boolean expression argument a list comprehension? */ - foreach(lc, bexpr->args) - { - Node *arg = lfirst(lc); - - if (has_a_cypher_list_comprehension_node(arg)) - { - return true; - } - } - break; - } - case T_A_Indirection: - { - /* set expr to the object of the indirection */ - expr = ((A_Indirection *)expr)->arg; - - /* check the object of the indirection */ - return has_a_cypher_list_comprehension_node(expr); - } - case T_ExtensibleNode: - { - if (is_ag_node(expr, cypher_unwind)) - { - cypher_unwind *cu = (cypher_unwind *)expr; - - /* it is a list comprehension if it has a collect node */ - return cu->collect != NULL; - } - else if (is_ag_node(expr, cypher_map)) - { - cypher_map *map; - int i; - - map = (cypher_map *)expr; - - if (map->keyvals == NULL || map->keyvals->length == 0) - { - return false; - } - - /* check each key and value for a list comprehension */ - for (i = 0; i < map->keyvals->length; i += 2) - { - Node *val; - - /* get the value */ - val = (Node *)map->keyvals->elements[i + 1].ptr_value; - - /* check the value */ - if (has_a_cypher_list_comprehension_node(val)) - { - return true; - } - } - } - else if (is_ag_node(expr, cypher_string_match)) - { - cypher_string_match *csm_match = (cypher_string_match *)expr; - - /* is lhs or rhs of the string match a list comprehension? */ - return (has_a_cypher_list_comprehension_node(csm_match->lhs) || - has_a_cypher_list_comprehension_node(csm_match->rhs)); - } - else if (is_ag_node(expr, cypher_typecast)) - { - cypher_typecast *ctypecast = (cypher_typecast *)expr; - - /* is expr being typecasted a list comprehension? */ - return has_a_cypher_list_comprehension_node(ctypecast->expr); - } - else if (is_ag_node(expr, cypher_comparison_aexpr)) - { - cypher_comparison_aexpr *aexpr = (cypher_comparison_aexpr *)expr; - - /* is left or right argument a list comprehension? */ - return (has_a_cypher_list_comprehension_node(aexpr->lexpr) || - has_a_cypher_list_comprehension_node(aexpr->rexpr)); - } - else if (is_ag_node(expr, cypher_comparison_boolexpr)) - { - cypher_comparison_boolexpr *bexpr = (cypher_comparison_boolexpr *)expr; - ListCell *lc; - - /* is any of the boolean expression argument a list comprehension? */ - foreach(lc, bexpr->args) - { - Node *arg = lfirst(lc); - - if (has_a_cypher_list_comprehension_node(arg)) - { - return true; - } - } - } - break; - } - default: - break; - } - /* otherwise, return false */ - return false; -} - /* see transformTargetList() */ List *transform_cypher_item_list(cypher_parsestate *cpstate, List *item_list, List **groupClause, ParseExprKind expr_kind) @@ -215,7 +68,6 @@ List *transform_cypher_item_list(cypher_parsestate *cpstate, List *item_list, { ResTarget *item = lfirst(li); TargetEntry *te; - bool has_list_comp = false; if (expand_star) { @@ -243,48 +95,14 @@ List *transform_cypher_item_list(cypher_parsestate *cpstate, List *item_list, } } } - - /* Check if we have a list comprehension */ - has_list_comp = has_a_cypher_list_comprehension_node(item->val); - /* Clear the exprHasAgg flag to check transform for an aggregate */ cpstate->exprHasAgg = false; - if (has_list_comp && item_list->length > 1) - { - /* - * Create a subquery for the list comprehension and transform it - * as a subquery. Then expand the target list of the subquery. - * This is to avoid multiple unnest functions in the same query - * level and collect not able to distinguish correctly. - */ - ParseNamespaceItem *pnsi; - cypher_return *cr; - cypher_clause cc; - - cr = make_ag_node(cypher_return); - cr->items = list_make1(item); - - cc.prev = NULL; - cc.next = NULL; - cc.self = (Node *)cr; - - pnsi = transform_cypher_clause_as_subquery(cpstate, - transform_cypher_clause, - &cc, NULL, true); - - target_list = list_concat(target_list, - expandNSItemAttrs(&cpstate->pstate, pnsi, - 0, true, -1)); - } - else - { - /* transform the item */ - te = transform_cypher_item(cpstate, item->val, NULL, expr_kind, - item->name, false); + /* transform the item */ + te = transform_cypher_item(cpstate, item->val, NULL, expr_kind, + item->name, false); - target_list = lappend(target_list, te); - } + target_list = lappend(target_list, te); /* * Did the transformed item contain an aggregate function? If it didn't, @@ -299,58 +117,6 @@ List *transform_cypher_item_list(cypher_parsestate *cpstate, List *item_list, { hasAgg = true; } - - /* - * This is for a special case with list comprehension, which is embedded - * in a cypher_unwind node. We need to group the results but not expose - * the grouping expression. - */ - if (has_list_comp) - { - ParseState *pstate = &cpstate->pstate; - ParseNamespaceItem *nsitem = NULL; - RangeTblEntry *rte = NULL; - - /* - * There should be at least 2 entries in p_namespace. One for the - * variable in the reading clause and one for the variable in the - * list_comprehension expression. Otherwise, there is nothing to - * group with. - */ - if (list_length(pstate->p_namespace) > 1) - { - /* - * Get the first namespace item which should be the first - * variable from the reading clause. - */ - nsitem = lfirst(list_head(pstate->p_namespace)); - /* extract the rte */ - rte = nsitem->p_rte; - - /* - * If we have a non-null column name make a ColumnRef to it. - * Otherwise, there wasn't a variable specified in the reading - * clause. If that is the case don't. Because there isn't - * anything to group with. - */ - if (rte->eref->colnames != NULL && nsitem->p_cols_visible) - { - ColumnRef *cref = NULL; - char *colname = NULL; - - /* get the name of the column (varname) */ - colname = strVal(lfirst(list_head(rte->eref->colnames))); - - /* create the ColumnRef */ - cref = makeNode(ColumnRef); - cref->fields = list_make1(makeString(colname)); - cref->location = -1; - - /* add the expression for grouping */ - group_clause = lappend(group_clause, cref); - } - } - } } /* diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index c0e14110..78559a36 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -1836,6 +1836,19 @@ static void array_to_agtype_internal(Datum array, agtype_in_state *result) pfree_if_not_null(nulls); } +PG_FUNCTION_INFO_V1(agtype_array_to_agtype); +Datum agtype_array_to_agtype(PG_FUNCTION_ARGS) +{ + agtype_in_state result; + + result.parse_state = NULL; + result.res = NULL; + + array_to_agtype_internal(PG_GETARG_DATUM(0), &result); + + PG_RETURN_POINTER(agtype_value_to_agtype(result.res)); +} + /* * Turn a composite / record into agtype. */ @@ -11910,7 +11923,6 @@ PG_FUNCTION_INFO_V1(age_unnest); Datum age_unnest(PG_FUNCTION_ARGS) { agtype *agtype_arg = NULL; - bool list_comprehension = false; ReturnSetInfo *rsi; Tuplestorestate *tuple_store; TupleDesc tupdesc; @@ -11921,35 +11933,13 @@ Datum age_unnest(PG_FUNCTION_ARGS) agtype_value v; agtype_iterator_token r; - /* verify that we have the correct number of args */ - if (PG_NARGS() != 2) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid number of arguments to unnest"))); - } - - /* verify that our flags are not null */ - if (PG_ARGISNULL(1)) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid unnest boolean flags passed"))); - } - /* check for a NULL expr */ if (PG_ARGISNULL(0)) { PG_RETURN_NULL(); } - /* get our flags */ - list_comprehension = PG_GETARG_BOOL(1); - - /* get the input expression */ agtype_arg = AG_GET_ARG_AGTYPE_P(0); - - /* verify that it resolves to an array */ if (!AGT_ROOT_IS_ARRAY(agtype_arg)) { ereport(ERROR, @@ -12006,25 +11996,6 @@ Datum age_unnest(PG_FUNCTION_ARGS) } } - /* - * If this is for list_comprehension, we need to add a NULL as the last row. - * This NULL will allow empty lists (either filtered out by where, creating - * an empty list, or just a generic empty list) to be preserved. - */ - if (list_comprehension) - { - Datum values[1] = {0}; - bool nulls[1] = {true}; - - old_cxt = MemoryContextSwitchTo(tmp_cxt); - - tuplestore_puttuple(tuple_store, - heap_form_tuple(ret_tdesc, values, nulls)); - - MemoryContextSwitchTo(old_cxt); - MemoryContextReset(tmp_cxt); - } - MemoryContextDelete(tmp_cxt); rsi->setResult = tuple_store; diff --git a/src/include/nodes/ag_nodes.h b/src/include/nodes/ag_nodes.h index fe9c9663..f0cc2204 100644 --- a/src/include/nodes/ag_nodes.h +++ b/src/include/nodes/ag_nodes.h @@ -50,6 +50,7 @@ typedef enum ag_node_tag cypher_map_projection_t, cypher_map_projection_element_t, cypher_list_t, + cypher_list_comprehension_t, /* comparison expression */ cypher_comparison_aexpr_t, cypher_comparison_boolexpr_t, @@ -106,6 +107,5 @@ static inline bool _is_ag_node(Node *node, const char *extnodename) } #define is_ag_node(node, type) _is_ag_node((Node *)(node), CppAsString(type)) -#define get_ag_node_tag(node) ((ag_node_tag)(((ExtensibleNode *)(node))->extnodename)) #endif diff --git a/src/include/nodes/cypher_nodes.h b/src/include/nodes/cypher_nodes.h index e7e293f3..0b5fec1c 100644 --- a/src/include/nodes/cypher_nodes.h +++ b/src/include/nodes/cypher_nodes.h @@ -67,7 +67,6 @@ typedef struct cypher_with { ExtensibleNode extensible; bool distinct; - bool subquery_intermediate; /* flag that denotes a subquery node */ List *items; /* a list of ResTarget's */ List *order_by; Node *skip; @@ -118,10 +117,6 @@ typedef struct cypher_unwind { ExtensibleNode extensible; ResTarget *target; - - /* for list comprehension */ - Node *where; - Node *collect; } cypher_unwind; typedef struct cypher_merge @@ -219,6 +214,15 @@ typedef struct cypher_map_projection int location; } cypher_map_projection; + typedef struct cypher_list_comprehension + { + ExtensibleNode extensible; + char *varname; + Node *expr; + Node *where; + Node *mapping_expr; + } cypher_list_comprehension; + typedef enum cypher_map_projection_element_type { PROPERTY_SELECTOR = 0, /* map_var { .key } */ diff --git a/src/include/nodes/cypher_outfuncs.h b/src/include/nodes/cypher_outfuncs.h index 4a04cf94..418d35f4 100644 --- a/src/include/nodes/cypher_outfuncs.h +++ b/src/include/nodes/cypher_outfuncs.h @@ -48,6 +48,7 @@ void out_cypher_param(StringInfo str, const ExtensibleNode *node); void out_cypher_map(StringInfo str, const ExtensibleNode *node); void out_cypher_map_projection(StringInfo str, const ExtensibleNode *node); void out_cypher_list(StringInfo str, const ExtensibleNode *node); +void out_cypher_list_comprehension(StringInfo str, const ExtensibleNode *node); /* comparison expression */ void out_cypher_comparison_aexpr(StringInfo str, const ExtensibleNode *node); diff --git a/src/include/parser/cypher_analyze.h b/src/include/parser/cypher_analyze.h index 97616e54..c74de0b0 100644 --- a/src/include/parser/cypher_analyze.h +++ b/src/include/parser/cypher_analyze.h @@ -21,16 +21,14 @@ #define AG_CYPHER_ANALYZE_H #include "parser/cypher_clause.h" - -typedef bool (*cypher_expression_condition)( Node *expr); +#include "nodes/nodeFuncs.h" void post_parse_analyze_init(void); void post_parse_analyze_fini(void); -cypher_clause *build_subquery_node(cypher_clause *next); - -/*expr tree walker */ -bool expr_contains_node(cypher_expression_condition is_expr, Node *expr); -bool expr_has_subquery(Node * expr); +bool cypher_expr_tree_walker(Node *node, bool (*walker) (), + void *context); +bool cypher_raw_expr_tree_walker(Node *node, bool (*walker) (), + void *context); #endif diff --git a/src/include/parser/cypher_clause.h b/src/include/parser/cypher_clause.h index 51ba5cb4..461a664c 100644 --- a/src/include/parser/cypher_clause.h +++ b/src/include/parser/cypher_clause.h @@ -39,13 +39,4 @@ Query *cypher_parse_sub_analyze(Node *parseTree, CommonTableExpr *parentCTE, bool locked_from_parent, bool resolve_unknowns); - -typedef Query *(*transform_method)(cypher_parsestate *cpstate, - cypher_clause *clause); - -ParseNamespaceItem *transform_cypher_clause_as_subquery(cypher_parsestate *cpstate, - transform_method transform, - cypher_clause *clause, - Alias *alias, - bool add_rte_to_query); #endif diff --git a/src/include/parser/cypher_item.h b/src/include/parser/cypher_item.h index ba4e7e9a..92b6c95f 100644 --- a/src/include/parser/cypher_item.h +++ b/src/include/parser/cypher_item.h @@ -26,6 +26,4 @@ TargetEntry *transform_cypher_item(cypher_parsestate *cpstate, Node *node, List *transform_cypher_item_list(cypher_parsestate *cpstate, List *item_list, List **groupClause, ParseExprKind expr_kind); -bool has_a_cypher_list_comprehension_node(Node *expr); - #endif diff --git a/src/include/parser/cypher_parse_node.h b/src/include/parser/cypher_parse_node.h index f7169380..263ea197 100644 --- a/src/include/parser/cypher_parse_node.h +++ b/src/include/parser/cypher_parse_node.h @@ -50,7 +50,6 @@ typedef struct cypher_parsestate */ bool exprHasAgg; bool p_opt_match; - bool p_list_comp; } cypher_parsestate; typedef struct errpos_ecb_state