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.
diff --git a/age--1.5.0--y.y.y.sql b/age--1.5.0--y.y.y.sql
index 85c2db7..6b7560a 100644
--- a/age--1.5.0--y.y.y.sql
+++ b/age--1.5.0--y.y.y.sql
@@ -137,4 +137,15 @@
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 bf5731d..07f7777 100644
--- a/regress/expected/list_comprehension.out
+++ b/regress/expected/list_comprehension.out
@@ -166,9 +166,9 @@
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 @@
------------
[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) 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) MATCH (v {list:[i IN u.list]}) RETURN v $$) AS (result agtype);
result
-----------------------------------------------------------------------------------------------
+ {"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": 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
- {"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
(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 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": 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": 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": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 11, 13]}}::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
(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 @@
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 @@
result
---------------------------------------------------------------------------------------------------------------------------------------------------------------
{"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 11, 13]}}::vertex
- {"id": 281474976710660, "label": "", "properties": {"list": [12, 14, 16, 18, 20, 22, 24]}}::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": 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": 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 @@
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 @@
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 cb941a6..572b2e6 100644
--- a/regress/sql/list_comprehension.sql
+++ b/regress/sql/list_comprehension.sql
@@ -117,9 +117,6 @@
-- 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 ={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 bdc33af..c7895fa 100644
--- a/sql/agtype_coercions.sql
+++ b/sql/agtype_coercions.sql
@@ -173,3 +173,14 @@
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 aa55140..c29c0a6 100644
--- a/sql/agtype_typecast.sql
+++ b/sql/agtype_typecast.sql
@@ -181,8 +181,7 @@
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 e20670b..9cab568 100644
--- a/src/backend/nodes/ag_nodes.c
+++ b/src/backend/nodes/ag_nodes.c
@@ -48,6 +48,7 @@
"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 @@
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 e245ba6..5bc824f 100644
--- a/src/backend/nodes/cypher_outfuncs.c
+++ b/src/backend/nodes/cypher_outfuncs.c
@@ -117,7 +117,6 @@
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);
@@ -176,9 +175,20 @@
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 a08cd30..5e43442 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 @@
/* 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 128acd0..4bba104 100644
--- a/src/backend/parser/cypher_analyze.c
+++ b/src/backend/parser/cypher_analyze.c
@@ -67,12 +67,6 @@
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 @@
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_impl(Node *node,
+ bool (*walker)(Node *node, void *context),
+ void *context)
{
- if (!expr)
- {
+ ListCell *temp;
+
+ if (node == NULL)
return false;
- }
- switch (nodeTag(expr))
+#define WALK(n) walker((Node *) (n), context)
+#define LIST_WALK(l) cypher_raw_expr_tree_walker_impl((Node *) (l), walker, context)
+
+ if (IsA(node, ExtensibleNode))
{
- case T_A_Const:
- case T_ColumnRef:
- case T_A_Indirection:
+ 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 */
{
- break;
+ return false;
}
- case T_A_Expr:
- {
- A_Expr *a = (A_Expr *)expr;
-
- switch (a->kind)
+ else if (is_ag_node(node, cypher_map))
{
- 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;
- }
-
- break;
- }
- case T_CaseTestExpr:
- {
- break;
- }
- case T_CoalesceExpr:
- {
- CoalesceExpr *cexpr = (CoalesceExpr *) expr;
- ListCell *args;
-
- foreach(args, cexpr->args)
- {
- Node *e = (Node *)lfirst(args);
-
- if (expr_contains_node(is_expr, e))
- {
- return true;
- }
- }
- break;
- }
- case T_ExtensibleNode:
- {
- if (is_ag_node(expr, cypher_bool_const))
- {
- 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);
- }
- if (is_ag_node(expr, 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 @@
val = lfirst(le);
- if (expr_contains_node(is_expr, val))
+ if (WALK(val))
{
return true;
}
@@ -845,216 +731,163 @@
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)
- {
- cypher_map_projection_element *elem;
-
- elem = lfirst(lc);
-
- if (expr_contains_node(is_expr, elem->value))
- {
- return true;
- }
- }
-
- break;
- }
- if (is_ag_node(expr, cypher_list))
- {
- cypher_list *cl = (cypher_list *)expr;
- ListCell *le = NULL;
-
- foreach(le, cl->elems)
- {
- Node *texpr = lfirst(le);
-
- if (expr_contains_node(is_expr, texpr))
- {
- return true;
- }
- }
- break;
- }
- if (is_ag_node(expr, cypher_string_match))
- {
- cypher_string_match *csm = (cypher_string_match *)expr;
-
- if (expr_contains_node(is_expr, csm->lhs) ||
- expr_contains_node(is_expr, csm->rhs))
+ if (LIST_WALK(cmp->map_elements))
{
return true;
}
- break;
}
- if (is_ag_node(expr, cypher_typecast))
+ else if (is_ag_node(node, cypher_list))
{
- cypher_typecast *t = (cypher_typecast *) expr;
-
- if (expr_contains_node(is_expr, t->expr))
+ cypher_list *cl = (cypher_list *)node;
+
+ if (LIST_WALK(cl->elems))
{
return true;
}
- break;
}
- if (is_ag_node(expr, cypher_comparison_aexpr))
+ else if (is_ag_node(node, cypher_string_match))
{
- cypher_comparison_aexpr *a = (cypher_comparison_aexpr *) expr;
+ cypher_string_match *csm = (cypher_string_match *)node;
- if (expr_contains_node(is_expr, a->lexpr) ||
- expr_contains_node(is_expr, a->rexpr))
+ if (WALK(csm->lhs))
{
return true;
}
- break;
- }
- if (is_ag_node(expr, cypher_comparison_boolexpr))
- {
- cypher_comparison_boolexpr *b = (cypher_comparison_boolexpr *) expr;
- ListCell *la;
- foreach(la, b->args)
- {
- Node *arg = lfirst(la);
-
- if (expr_contains_node(is_expr, arg))
- {
- return true;
- }
- }
- break;
- }
- if (is_ag_node(expr, cypher_unwind))
- {
- cypher_unwind* lc = (cypher_unwind *)expr;
-
- if (expr_contains_node(is_expr, lc->where) ||
- expr_contains_node(is_expr, lc->collect))
+ if (WALK(csm->rhs))
{
return true;
}
- break;
}
-
- if (is_ag_node(expr, cypher_sub_pattern))
+ else if (is_ag_node(node, cypher_typecast))
{
- break;
- }
+ cypher_typecast *t = (cypher_typecast *)node;
- if (is_ag_node(expr, cypher_sub_query))
+ if (WALK(t->expr))
+ {
+ return true;
+ }
+ }
+ else if (is_ag_node(node, cypher_comparison_aexpr))
{
- break;
+ cypher_comparison_aexpr *a = (cypher_comparison_aexpr *)node;
+
+ if (WALK(a->lexpr))
+ {
+ return true;
+ }
+
+ if (WALK(a->rexpr))
+ {
+ return true;
+ }
+ }
+ else if (is_ag_node(node, cypher_comparison_boolexpr))
+ {
+ cypher_comparison_boolexpr *b = (cypher_comparison_boolexpr *)node;
+
+ if (LIST_WALK(b->args))
+ {
+ return true;
+ }
+ }
+ else if (is_ag_node(node, cypher_unwind))
+ {
+ cypher_unwind *unw = (cypher_unwind *)node;
+
+ if (WALK(unw->target))
+ {
+ return true;
+ }
}
- ereport(ERROR,
+ else if (is_ag_node(node, cypher_list_comprehension))
+ {
+ cypher_list_comprehension *lc = (cypher_list_comprehension *)node;
+
+ if (WALK(lc->expr))
+ {
+ return true;
+ }
+
+ if (WALK(lc->where))
+ {
+ return true;
+ }
+
+ if (WALK(lc->mapping_expr))
+ {
+ return true;
+ }
+ }
+ /* Add more node types here as needed */
+ else
+ {
+ ereport(ERROR,
(errmsg_internal("unrecognized ExtensibleNode: %s",
- ((ExtensibleNode *)expr)->extnodename)));
-
- break;
+ ((ExtensibleNode *)node)->extnodename)));
+ }
}
- case T_FuncCall:
+ /*
+ * postgres's raw expresssion tree walker does not handle List
+ */
+ else if (IsA(node, List))
{
- FuncCall *fn = (FuncCall *)expr;
- ListCell *arg;
-
- foreach(arg, fn->args)
+ foreach(temp, (List *) node)
{
- Node *farg = NULL;
-
- farg = (Node *)lfirst(arg);
-
- if (expr_contains_node(is_expr, farg))
- {
+ if (WALK((Node *) lfirst(temp)))
return true;
- }
- }
- break;
- }
- case T_SubLink:
- {
- SubLink *s = (SubLink *)expr;
-
- if (expr_contains_node(is_expr, s->subselect))
- {
- return true;
- }
- break;
- }
- default:
- ereport(ERROR, (errmsg_internal("unrecognized node type: %d",
- nodeTag(expr))));
- }
-
- return (is_expr(expr));
-}
-
-/*
- * 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.
- */
-
-bool expr_has_subquery(Node * expr)
-{
- if (expr == NULL)
- {
- return false;
- }
-
- if (IsA(expr, ExtensibleNode))
- {
- if (is_ag_node(expr, cypher_sub_query))
- {
- return true;
}
}
+
+#undef LIST_WALK
+ else
+ {
+ return raw_expression_tree_walker(node, walker, context);
+ }
+
return false;
}
/*
- * This function constructs an intermediate WITH node for processing subqueries
+ * 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.
*/
-cypher_clause *build_subquery_node(cypher_clause *next)
+bool cypher_expr_tree_walker_impl(Node *node,
+ bool (*walker)(Node *node, void *context),
+ void *context)
{
- 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;
+ if (node == NULL)
+ {
+ return false;
+ }
- /* construct the column ref star */
- cr = makeNode(ColumnRef);
- cr->fields = list_make1(makeNode(A_Star));
- cr->location = exprLocation((Node *)next);
+#define LIST_WALK(l) cypher_expr_tree_walker_impl((Node *) (l), walker, context)
- /*construct the restarget */
- rt = makeNode(ResTarget);
- rt->name = NULL;
- rt->indirection = NIL;
- rt->val = (Node *)cr;
- rt->location = exprLocation((Node *)next);
+ if (IsA(node, ExtensibleNode))
+ {
+ /* Add our nodes that can appear post parsing stage */
+ ereport(ERROR,
+ (errmsg_internal("unrecognized ExtensibleNode: %s",
+ ((ExtensibleNode *)node)->extnodename)));
+ }
+#undef WALK
+#undef LIST_WALK
+ else
+ {
+ return expression_tree_walker(node, walker, context);
+ }
- /* construct the with_clause */
- with_clause->items = list_make1(rt);
- with_clause->where = match->where;
- with_clause->subquery_intermediate = true;
-
- /*
- * 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;
-
- return where_container_clause;
+ return false;
}
static Query *analyze_cypher(List *stmt, ParseState *parent_pstate,
@@ -1083,23 +916,6 @@
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 e301daa..172e630 100644
--- a/src/backend/parser/cypher_clause.c
+++ b/src/backend/parser/cypher_clause.c
@@ -248,6 +248,11 @@
/* 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 @@
#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,
@@ -328,9 +333,9 @@
/* 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,
@@ -338,6 +343,7 @@
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
@@ -400,19 +406,16 @@
}
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")));
@@ -448,23 +451,6 @@
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;
@@ -663,6 +649,7 @@
qry->rteperminfos = pstate->p_rteperminfos;
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
qry->hasAggs = pstate->p_hasAggs;
+ qry->hasSubLinks = pstate->p_hasSubLinks;
assign_query_collations(pstate, qry);
@@ -1268,6 +1255,7 @@
query->rteperminfos = cpstate->pstate.p_rteperminfos;
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);
@@ -1340,6 +1328,8 @@
query->rtable = pstate->p_rtable;
query->rteperminfos = pstate->p_rteperminfos;
query->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+ query->hasAggs = pstate->p_hasAggs;
+ query->hasSubLinks = pstate->p_hasSubLinks;
return query;
}
@@ -1363,9 +1353,6 @@
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;
@@ -1394,13 +1381,14 @@
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"),
@@ -1413,12 +1401,11 @@
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++,
@@ -1430,6 +1417,7 @@
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);
@@ -1437,6 +1425,88 @@
}
/*
+ * [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->rteperminfos = child_pstate->p_rteperminfos;
+ 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.
*/
@@ -1572,6 +1642,8 @@
query->rtable = pstate->p_rtable;
query->rteperminfos = pstate->p_rteperminfos;
query->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+ query->hasAggs = pstate->p_hasAggs;
+ query->hasSubLinks = pstate->p_hasSubLinks;
return query;
}
@@ -1845,12 +1917,7 @@
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"),
@@ -2182,6 +2249,7 @@
query->rteperminfos = pstate->p_rteperminfos;
query->jointree = makeFromExpr(pstate->p_joinlist, NULL);
query->hasAggs = pstate->p_hasAggs;
+ query->hasSubLinks = pstate->p_hasSubLinks;
assign_query_collations(pstate, query);
@@ -2370,7 +2438,6 @@
{
ParseState *pstate = (ParseState *)cpstate;
Query *query;
- Node *self = clause->self;
Node *where_qual = NULL;
if (where)
@@ -2383,10 +2450,8 @@
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)
{
@@ -2401,79 +2466,15 @@
* next clause
*/
query->targetList = expandNSItemAttrs(pstate, pnsi, 0, true, -1);
-
markTargetListOrigins(pstate, query->targetList);
-
query->rtable = pstate->p_rtable;
query->rteperminfos = pstate->p_rteperminfos;
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
{
@@ -2484,8 +2485,6 @@
query->hasTargetSRFs = pstate->p_hasTargetSRFs;
query->hasAggs = pstate->p_hasAggs;
- assign_query_collations(pstate, query);
-
return query;
}
@@ -2493,6 +2492,7 @@
cypher_clause *clause)
{
cypher_match *match_self = (cypher_match*) clause->self;
+ Node *where = match_self->where;
if(!match_check_valid_label(match_self, cpstate))
{
@@ -2510,10 +2510,40 @@
(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
* for the join to use.
@@ -2709,18 +2739,6 @@
{
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;
@@ -2983,9 +3001,9 @@
* 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;
@@ -3053,7 +3071,7 @@
* 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);
@@ -3246,36 +3264,7 @@
query->rtable = cpstate->pstate.p_rtable;
query->rteperminfos = cpstate->pstate.p_rteperminfos;
-
- 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);
}
/*
@@ -5645,6 +5634,7 @@
query->rteperminfos = pstate->p_rteperminfos;
query->jointree = makeFromExpr(pstate->p_joinlist, NULL);
query->hasAggs = pstate->p_hasAggs;
+ query->hasSubLinks = pstate->p_hasSubLinks;
return query;
}
@@ -6280,7 +6270,7 @@
* 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,
@@ -6302,8 +6292,7 @@
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
@@ -6507,6 +6496,12 @@
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;
@@ -6657,7 +6652,6 @@
query->rteperminfos = pstate->p_rteperminfos;
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 16d6e35..19bc71d 100644
--- a/src/backend/parser/cypher_expr.c
+++ b/src/backend/parser/cypher_expr.c
@@ -98,8 +98,6 @@
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 @@
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 @@
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))));
@@ -391,26 +385,8 @@
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;
@@ -1322,7 +1298,7 @@
}
/* 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);
/*
@@ -2373,6 +2349,7 @@
case EXPR_KIND_SELECT_TARGET:
case EXPR_KIND_FROM_SUBSELECT:
case EXPR_KIND_WHERE:
+ case EXPR_KIND_INSERT_TARGET:
/* okay */
break;
default:
@@ -2412,7 +2389,7 @@
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
@@ -2444,31 +2421,3 @@
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 6cb15e5..a4d8f0f 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"
@@ -263,15 +264,9 @@
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);
%}
%%
@@ -1062,8 +1057,6 @@
n = make_ag_node(cypher_unwind);
n->target = res;
- n->where = NULL;
- n->collect = NULL;
$$ = (Node *) n;
}
@@ -2067,6 +2060,7 @@
| map
| map_projection
| list
+ | list_comprehension
;
map:
@@ -2186,7 +2180,6 @@
$$ = (Node *)n;
}
- | list_comprehension
;
/*
@@ -2197,40 +2190,28 @@
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);
}
;
@@ -2881,6 +2862,10 @@
{
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,
@@ -3275,90 +3260,51 @@
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;
+ sub = makeNode(SubLink);
+ sub->subLinkType = ARRAY_SUBLINK;
+ sub->subLinkId = 0;
+ sub->testexpr = NULL;
+ sub->subselect = (Node *)list_comp;
+ sub->location = location;
- /* 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;
+ 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 c32b46c..c2feb27 100644
--- a/src/backend/parser/cypher_item.c
+++ b/src/backend/parser/cypher_item.c
@@ -27,17 +27,14 @@
#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_pnsi_attrs(ParseState *pstate, ParseNamespaceItem *pnsi,
int sublevels_up, bool require_col_privs,
int location);
-bool has_a_cypher_list_comprehension_node(Node *expr);
/* see transformTargetEntry() */
TargetEntry *transform_cypher_item(cypher_parsestate *cpstate, Node *node,
@@ -45,17 +42,10 @@
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);
@@ -63,143 +53,6 @@
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)
@@ -216,7 +69,6 @@
{
ResTarget *item = lfirst(li);
TargetEntry *te;
- bool has_list_comp = false;
if (expand_star)
{
@@ -244,48 +96,14 @@
}
}
}
-
- /* 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;
+ /* transform the item */
+ te = transform_cypher_item(cpstate, item->val, NULL, expr_kind,
+ item->name, false);
- 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);
-
- target_list = lappend(target_list, te);
- }
+ target_list = lappend(target_list, te);
/*
* Did the transformed item contain an aggregate function? If it didn't,
@@ -300,58 +118,6 @@
{
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 090b178..86f41f2 100644
--- a/src/backend/utils/adt/agtype.c
+++ b/src/backend/utils/adt/agtype.c
@@ -1838,6 +1838,19 @@
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.
*/
@@ -11916,7 +11929,6 @@
Datum age_unnest(PG_FUNCTION_ARGS)
{
agtype *agtype_arg = NULL;
- bool list_comprehension = false;
ReturnSetInfo *rsi;
Tuplestorestate *tuple_store;
TupleDesc tupdesc;
@@ -11927,35 +11939,13 @@
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,
@@ -12012,25 +12002,6 @@
}
}
- /*
- * 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 fe9c966..f0cc220 100644
--- a/src/include/nodes/ag_nodes.h
+++ b/src/include/nodes/ag_nodes.h
@@ -50,6 +50,7 @@
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 @@
}
#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 fc9a974..f252701 100644
--- a/src/include/nodes/cypher_nodes.h
+++ b/src/include/nodes/cypher_nodes.h
@@ -68,7 +68,6 @@
{
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;
@@ -119,10 +118,6 @@
{
ExtensibleNode extensible;
ResTarget *target;
-
- /* for list comprehension */
- Node *where;
- Node *collect;
} cypher_unwind;
typedef struct cypher_merge
@@ -220,6 +215,15 @@
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 4a04cf9..418d35f 100644
--- a/src/include/nodes/cypher_outfuncs.h
+++ b/src/include/nodes/cypher_outfuncs.h
@@ -48,6 +48,7 @@
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 97616e5..b45a468 100644
--- a/src/include/parser/cypher_analyze.h
+++ b/src/include/parser/cypher_analyze.h
@@ -21,16 +21,22 @@
#define AG_CYPHER_ANALYZE_H
#include "parser/cypher_clause.h"
+#include "nodes/nodeFuncs.h"
-typedef bool (*cypher_expression_condition)( Node *expr);
+#define cypher_expr_tree_walker(n, w, c) \
+ cypher_expr_tree_walker_impl(n, (tree_walker_callback) (w), c)
+#define cypher_raw_expr_tree_walker(n, w, c) \
+ cypher_raw_expr_tree_walker_impl(n, (tree_walker_callback) (w), c)
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);
+/* expr tree walker */
+bool cypher_expr_tree_walker_impl(Node *node,
+ bool (*walker)(Node *node, void *context),
+ void *context);
+bool cypher_raw_expr_tree_walker_impl(Node *node,
+ bool (*walker)(Node *node, void *context),
+ void *context);
#endif
diff --git a/src/include/parser/cypher_clause.h b/src/include/parser/cypher_clause.h
index 51ba5cb..461a664 100644
--- a/src/include/parser/cypher_clause.h
+++ b/src/include/parser/cypher_clause.h
@@ -39,13 +39,4 @@
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 ba4e7e9..92b6c95 100644
--- a/src/include/parser/cypher_item.h
+++ b/src/include/parser/cypher_item.h
@@ -26,6 +26,4 @@
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 f716938..263ea19 100644
--- a/src/include/parser/cypher_parse_node.h
+++ b/src/include/parser/cypher_parse_node.h
@@ -50,7 +50,6 @@
*/
bool exprHasAgg;
bool p_opt_match;
- bool p_list_comp;
} cypher_parsestate;
typedef struct errpos_ecb_state