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

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


The following commit(s) were added to refs/heads/PG14 by this push:
     new 080126f5 Implement list comprehension (#1738)
080126f5 is described below

commit 080126f5fb010c9c3cd98ce096b7306ce6e793c3
Author: Muhammad Taha Naveed <[email protected]>
AuthorDate: Tue Apr 9 12:11:09 2024 +0500

    Implement list comprehension (#1738)
    
    * Implement list comprehension
    
    * Add initial code for list comprehension
    
    - Additionally, fixed a bug in nested queries.
    
    * Resolve variable scope and ambigous variable issue
    
    - For scoping the variable, remove the pnsi from namespace list after
      being used by the list comprehension.
    
    - To resolve the ambigous variable issue, only scan the pnsi of
      list_comp subquery.
    
    * Fix non agg plan node issue in list comprehension
    
    - Resolved the issue 'aggref found in non-agg plan node'
      when the list comprehension is used as a property constraint in
      MATCH and other clauses or when used in WHERE clause. Intead of adding
      qual in the jointree, we are now adding the qual in having node of
      query.
    
      e.g.
      MATCH (n {list: [for i in [1,2,3]]})
      MATCH (n) WHERE n.list=[for i in [1,2,3]]
      WITH n WHERE n.list=[for i in [1,2,3]]
    
    - Removed redundant call to function tranform_cypher_clause_with_where
      by tranform_match_clause.
    
    * Resolve variable reuse issue in list comprehension
    
    - Also, for scoping list comprehension variable, instead of removing
      the pnsi of list comp from namespace, now we just mark the cols of
      pnsi as not visible.
    
    * Modify grammer to match opencypher list comprehension grammer
    
    - Also added initial regression tests for list comprehension
    
    * Add regression tests for bugs resolved
    
    - Includes tests for nested cases, ambigous variable issue, list
      comprehension variable scoping and planner node aggref issue.
    
    - Should be added more.
    
    * Resolve invalid output on multiple list comprehensions
    
    - Fix the output of multiple list comprehensions in the RETURN clause
      and the WHERE clause.
    - Added regression tests for the above and some for the previous bugs
      resolved.
    
    * Fix aggref found where not expected issue (#189)
    
    - Resolved the 'aggref found where not expected' error thrown when
      using list comprehensions in UNWIND, or as a property update
      expression in SET or in the comparison expressions, typecasts or
      string matching in WITH - WHERE clause
    
    - Added an error to be thrown when using aggregation functions in UNWIND
      and property update expr in SET clause
    
    - Added regression tests
    
    * cleanup: Remove dead code and formatting changes
    
    ---------
    
    Co-authored-by: John Gemignani <[email protected]>
    Co-authored-by: Zainab Saad <[email protected]>
    
    * Fix shift/reduce conflict in grammar
    
    - The grammar had a shift/reduce conflict due to the ambiguity of the
      `IN` keyword. This is resolved by adding generic rule and manually
      resolving to the correct specific rule.
    - Added additional test cases.
    
    ---------
    
    Co-authored-by: John Gemignani <[email protected]>
    Co-authored-by: Zainab Saad <[email protected]>
---
 Makefile                                |   1 +
 regress/expected/list_comprehension.out | 594 ++++++++++++++++++++++++++++++++
 regress/sql/list_comprehension.sql      | 152 ++++++++
 sql/agtype_typecast.sql                 |   3 +-
 src/backend/nodes/cypher_outfuncs.c     |   1 +
 src/backend/parser/cypher_clause.c      | 124 ++++++-
 src/backend/parser/cypher_expr.c        | 110 +++++-
 src/backend/parser/cypher_gram.y        | 151 ++++++++
 src/backend/parser/cypher_item.c        | 243 ++++++++++++-
 src/backend/utils/adt/agtype.c          |  44 ++-
 src/include/nodes/ag_nodes.h            |   1 +
 src/include/nodes/cypher_nodes.h        |   4 +
 src/include/parser/cypher_clause.h      |   9 +
 src/include/parser/cypher_item.h        |   2 +
 src/include/parser/cypher_parse_node.h  |   1 +
 15 files changed, 1413 insertions(+), 27 deletions(-)

diff --git a/Makefile b/Makefile
index 957c9a61..ef97774b 100644
--- a/Makefile
+++ b/Makefile
@@ -110,6 +110,7 @@ REGRESS = scan \
           graph_generation \
           name_validation \
           jsonb_operators \
+          list_comprehension \
           drop
 
 srcdir=`pwd`
diff --git a/regress/expected/list_comprehension.out 
b/regress/expected/list_comprehension.out
new file mode 100644
index 00000000..d6d69096
--- /dev/null
+++ b/regress/expected/list_comprehension.out
@@ -0,0 +1,594 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+LOAD 'age';
+SET search_path TO ag_catalog;
+SELECT create_graph('list_comprehension');
+NOTICE:  graph "list_comprehension" has been created
+ create_graph 
+--------------
+ 
+(1 row)
+
+SELECT * from cypher('list_comprehension', $$ CREATE (u {list: [0, 2, 4, 6, 8, 
10, 12]}) RETURN u $$) AS (result agtype);
+                                            result                             
                
+-----------------------------------------------------------------------------------------------
+ {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 
10, 12]}}::vertex
+(1 row)
+
+SELECT * from cypher('list_comprehension', $$ CREATE (u {list: [1, 3, 5, 7, 9, 
11, 13]}) RETURN u $$) AS (result agtype);
+                                            result                             
                
+-----------------------------------------------------------------------------------------------
+ {"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 
11, 13]}}::vertex
+(1 row)
+
+SELECT * from cypher('list_comprehension', $$ CREATE (u {list: []}) RETURN u 
$$) AS (result agtype);
+                                  result                                  
+--------------------------------------------------------------------------
+ {"id": 281474976710659, "label": "", "properties": {"list": []}}::vertex
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2)] 
$$) AS (result agtype);
+                         result                          
+---------------------------------------------------------
+ [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2)][2] 
$$) AS (result agtype);
+ result 
+--------
+ 5
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 
2)][1..4] $$) AS (result agtype);
+  result   
+-----------
+ [3, 5, 7]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) 
WHERE u % 3 = 0] $$) AS (result agtype);
+       result       
+--------------------
+ [3, 9, 15, 21, 27]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) 
WHERE u % 3 = 0][2] $$) AS (result agtype);
+ result 
+--------
+ 15
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) 
WHERE u % 3 = 0][0..4] $$) AS (result agtype);
+     result     
+----------------
+ [3, 9, 15, 21]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) 
WHERE u % 3 = 0 | u^2 ] $$) AS (result agtype);
+              result              
+----------------------------------
+ [9.0, 81.0, 225.0, 441.0, 729.0]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) 
WHERE u % 3 = 0 | u^2 ][3] $$) AS (result agtype);
+ result 
+--------
+ 441.0
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) 
WHERE u % 3 = 0 | u^2 ][1..5] $$) AS (result agtype);
+           result            
+-----------------------------
+ [81.0, 225.0, 441.0, 729.0]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) | 
u^2 ] $$) AS (result agtype);
+                                               result                          
                     
+----------------------------------------------------------------------------------------------------
+ [1.0, 9.0, 25.0, 49.0, 81.0, 121.0, 169.0, 225.0, 289.0, 361.0, 441.0, 529.0, 
625.0, 729.0, 841.0]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) | 
u^2 ][0] $$) AS (result agtype);
+ result 
+--------
+ 1.0
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) | 
u^2 ][0..2] $$) AS (result agtype);
+   result   
+------------
+ [1.0, 9.0]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN (u) $$) 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": 281474976710659, "label": "", "properties": {"list": []}}::vertex
+(3 rows)
+
+SELECT * FROM cypher('list_comprehension', $$ MATCH () RETURN [i IN range(0, 
20, 2) WHERE i % 3 = 0 ] $$) AS (result agtype);
+     result     
+----------------
+ [0, 6, 12, 18]
+ [0, 6, 12, 18]
+ [0, 6, 12, 18]
+(3 rows)
+
+SELECT * FROM cypher('list_comprehension', $$ MATCH () RETURN [i IN range(0, 
20, 2) WHERE i % 3 = 0 | i^2 ] $$) AS (result agtype);
+          result           
+---------------------------
+ [0.0, 36.0, 144.0, 324.0]
+ [0.0, 36.0, 144.0, 324.0]
+ [0.0, 36.0, 144.0, 324.0]
+(3 rows)
+
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN range(0, 
20, 2) WHERE i % 3 = 0 | i^2 ] $$) AS (result agtype);
+          result           
+---------------------------
+ [0.0, 36.0, 144.0, 324.0]
+ [0.0, 36.0, 144.0, 324.0]
+ [0.0, 36.0, 144.0, 324.0]
+(3 rows)
+
+SELECT * FROM cypher('list_comprehension', $$ MATCH p=() RETURN [i IN range(0, 
20, 2) WHERE i % 3 = 0 | i^2 ] $$) AS (result agtype);
+          result           
+---------------------------
+ [0.0, 36.0, 144.0, 324.0]
+ [0.0, 36.0, 144.0, 324.0]
+ [0.0, 36.0, 144.0, 324.0]
+(3 rows)
+
+SELECT * FROM cypher('list_comprehension', $$ MATCH p=(u) RETURN [i IN 
range(0, 20, 2) WHERE i % 3 = 0 | i^2 ] $$) AS (result agtype);
+          result           
+---------------------------
+ [0.0, 36.0, 144.0, 324.0]
+ [0.0, 36.0, 144.0, 324.0]
+ [0.0, 36.0, 144.0, 324.0]
+(3 rows)
+
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list] 
$$) AS (result agtype);
+         result          
+-------------------------
+ [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);
+   result   
+------------
+ [0, 6, 12]
+ [3, 9]
+(2 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)
+
+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)
+
+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)
+
+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)
+
+-- Nested cases
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3]]] $$) 
AS (result agtype);
+  result   
+-----------
+ [1, 2, 3]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [i IN 
[1,2,3]]]] $$) AS (result agtype);
+  result   
+-----------
+ [1, 2, 3]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE 
i>1]] $$) AS (result agtype);
+ result 
+--------
+ [2, 3]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3]] 
WHERE i>1] $$) AS (result agtype);
+ result 
+--------
+ [2, 3]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE 
i>1] WHERE i>2] $$) AS (result agtype);
+ result 
+--------
+ [3]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE 
i>1 | i^2]] $$) AS (result agtype);
+   result   
+------------
+ [4.0, 9.0]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3]] 
WHERE i>1 | i^2] $$) AS (result agtype);
+   result   
+------------
+ [4.0, 9.0]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE 
i>1] WHERE i>2 | i^2] $$) AS (result agtype);
+ result 
+--------
+ [9.0]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE 
i>1 | i^2] WHERE i>4] $$) AS (result agtype);
+ result 
+--------
+ [9.0]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE 
i>1 | i^2] WHERE i>4 | i^2] $$) AS (result agtype);
+ result 
+--------
+ [81.0]
+(1 row)
+
+-- List comprehension inside where and property constraints
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list=[i IN 
range(0,12,2)] RETURN u $$) AS (result agtype);
+                                            result                             
                
+-----------------------------------------------------------------------------------------------
+ {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 
10, 12]}}::vertex
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list=[i IN 
range(1,13,2)] RETURN u $$) AS (result agtype);
+                                            result                             
                
+-----------------------------------------------------------------------------------------------
+ {"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 
11, 13]}}::vertex
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list=[i IN 
range(0,12,2) WHERE i>4] RETURN u $$) AS (result agtype);
+ result 
+--------
+(0 rows)
+
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list=[i IN 
range(1,13,2) WHERE i>4 | i^1] RETURN u $$) AS (result agtype);
+ result 
+--------
+(0 rows)
+
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list@>[i IN 
range(0,6,2)] RETURN u $$) AS (result agtype);
+                                            result                             
                
+-----------------------------------------------------------------------------------------------
+ {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 
10, 12]}}::vertex
+(1 row)
+
+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
+(3 rows)
+
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list:[i IN 
range(0,12,2)]}) RETURN u $$) AS (result agtype);
+                                            result                             
                
+-----------------------------------------------------------------------------------------------
+ {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 
10, 12]}}::vertex
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list:[i IN 
range(1,13,2)]}) RETURN u $$) AS (result agtype);
+                                            result                             
                
+-----------------------------------------------------------------------------------------------
+ {"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 
11, 13]}}::vertex
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list:[i IN 
range(0,12,2) WHERE i>4]}) RETURN u $$) AS (result agtype);
+                                            result                             
                
+-----------------------------------------------------------------------------------------------
+ {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 
10, 12]}}::vertex
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list:[i IN 
range(1,13,2) WHERE i>4 | i^1]}) RETURN u $$) AS (result agtype);
+ result 
+--------
+(0 rows)
+
+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": 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);
+                                               result                          
                     
+----------------------------------------------------------------------------------------------------
+ {"id": 281474976710660, "label": "", "properties": {"list": [12, 14, 16, 18, 
20, 22, 24]}}::vertex
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ CREATE (u {list:[i IN 
range(0,12,2) WHERE i>4]}) RETURN u $$) AS (result agtype);
+                                        result                                 
       
+--------------------------------------------------------------------------------------
+ {"id": 281474976710661, "label": "", "properties": {"list": [6, 8, 10, 
12]}}::vertex
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ CREATE (u {list:[i IN 
range(1,13,2) WHERE i>4 | i^2]}) RETURN u $$) AS (result agtype);
+                                                 result                        
                         
+--------------------------------------------------------------------------------------------------------
+ {"id": 281474976710662, "label": "", "properties": {"list": [25.0, 49.0, 
81.0, 121.0, 169.0]}}::vertex
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ WITH [u IN [1,2,3]] AS a CREATE 
(u {list:a}) RETURN u $$) AS (result agtype);
+                                     result                                    
  
+---------------------------------------------------------------------------------
+ {"id": 281474976710663, "label": "", "properties": {"list": [1, 2, 
3]}}::vertex
+(1 row)
+
+-- List comprehension in the WITH WHERE clause
+SELECT * FROM cypher('list_comprehension', $$ MATCH(u) WITH u WHERE u.list = 
[u IN [0, 2, 4, 6, 8, 10, 12]] RETURN u $$) AS (u agtype);
+                                               u                               
                
+-----------------------------------------------------------------------------------------------
+ {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 
10, 12]}}::vertex
+(1 row)
+
+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": 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": 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": 281474976710663, "label": "", "properties": {"list": [1, 2, 
3]}}::vertex
+ {"id": 281474976710661, "label": "", "properties": {"list": [6, 8, 10, 
12]}}::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": 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);
+ u 
+---
+(0 rows)
+
+SELECT * FROM cypher('list_comprehension', $$ MATCH(u: csm_match) WITH u WHERE 
[u IN u.list][0] STARTS WITH "ab" RETURN u $$) AS (u agtype);
+                                                  u                            
                       
+------------------------------------------------------------------------------------------------------
+ {"id": 844424930131969, "label": "csm_match", "properties": {"list": ["abc", 
"def", "ghi"]}}::vertex
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ WITH [1, 2, 3] AS u WHERE u = [u 
IN [1, 2, 3]] RETURN u $$) AS (u agtype);
+     u     
+-----------
+ [1, 2, 3]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ WITH 1 AS u WHERE u = [u IN [1, 
2, 3]][0] RETURN u $$) AS (u agtype);
+ u 
+---
+ 1
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ WITH ['abc', 'defgh'] AS u WHERE 
[v In u][1] STARTS WITH 'de' RETURN u $$) AS (u agtype);
+        u         
+------------------
+ ["abc", "defgh"]
+(1 row)
+
+-- List comprehension in UNWIND
+SELECT * FROM cypher('list_comprehension', $$ UNWIND [u IN [1, 2, 3]] AS u 
RETURN u $$) AS (u agtype);
+ u 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+SELECT * FROM cypher('list_comprehension', $$ UNWIND [u IN [1, 2, 3] WHERE u > 
1 | u*2] AS u RETURN u $$) AS (u agtype);
+ u 
+---
+ 4
+ 6
+(2 rows)
+
+-- invalid use of aggregation in UNWIND
+SELECT * FROM cypher('list_comprehension', $$ WITH [1, 2, 3] AS u UNWIND 
collect(u) AS v RETURN v $$) AS (u agtype);
+ERROR:  Invalid use of aggregation in this context
+LINE 1: ...ist_comprehension', $$ WITH [1, 2, 3] AS u UNWIND collect(u)...
+                                                             ^
+-- List comprehension in SET
+SELECT * FROM cypher('list_comprehension', $$ MATCH(u {list: [0, 2, 4, 6, 8, 
10, 12]}) SET u.a = [u IN range(0, 5)] RETURN u $$) AS (u agtype);
+                                                           u                   
                                         
+------------------------------------------------------------------------------------------------------------------------
+ {"id": 281474976710657, "label": "", "properties": {"a": [0, 1, 2, 3, 4, 5], 
"list": [0, 2, 4, 6, 8, 10, 12]}}::vertex
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ MATCH(u {list: [0, 2, 4, 6, 8, 
10, 12]}) SET u.a = [u IN []] RETURN u $$) AS (u agtype);
+                                                   u                           
                         
+--------------------------------------------------------------------------------------------------------
+ {"id": 281474976710657, "label": "", "properties": {"a": [], "list": [0, 2, 
4, 6, 8, 10, 12]}}::vertex
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ MATCH(u {list: [0, 2, 4, 6, 8, 
10, 12]}) SET u += {b: [u IN range(0, 5)]} RETURN u $$) AS (u agtype);
+                                                                u              
                                                  
+---------------------------------------------------------------------------------------------------------------------------------
+ {"id": 281474976710657, "label": "", "properties": {"a": [], "b": [0, 1, 2, 
3, 4, 5], "list": [0, 2, 4, 6, 8, 10, 12]}}::vertex
+(1 row)
+
+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 (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
+(1 row)
+
+-- 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);
+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   
+-----------
+ [1, 2, 3]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ WITH 1 AS m RETURN [m IN [1, 2, 
3]], m $$) AS (result agtype, result2 agtype);
+  result   | result2 
+-----------+---------
+ [1, 2, 3] | 1
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ WITH [m IN [1,2,3]] AS m RETURN 
[m IN [1, 2, 3]], m $$) AS (result agtype, result2 agtype);
+  result   |  result2  
+-----------+-----------
+ [1, 2, 3] | [1, 2, 3]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ CREATE n=()-[:edge]->() RETURN 
[n IN nodes(n)] $$) AS (u agtype);
+                                                                u              
                                                   
+----------------------------------------------------------------------------------------------------------------------------------
+ [{"id": 281474976710664, "label": "", "properties": {}}::vertex, {"id": 
281474976710665, "label": "", "properties": {}}::vertex]
+(1 row)
+
+-- Multiple list comprehensions in RETURN and WITH clause
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3]], [u IN 
[1,2,3]] $$) AS (result agtype, result2 agtype);
+  result   |  result2  
+-----------+-----------
+ [1, 2, 3] | [1, 2, 3]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3] WHERE u>1], 
[u IN [1,2,3] WHERE u>2] $$) AS (result agtype, result2 agtype);
+ result | result2 
+--------+---------
+ [2, 3] | [3]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3] WHERE u>1 | 
u^3], [u IN [1,2,3] WHERE u>2 | u^3] $$) AS (result agtype, result2 agtype);
+   result    | result2 
+-------------+---------
+ [8.0, 27.0] | [27.0]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3]] AS u, [u 
IN [1,2,3]] AS i $$) AS (result agtype, result2 agtype);
+  result   |  result2  
+-----------+-----------
+ [1, 2, 3] | [1, 2, 3]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3] WHERE u>1] 
AS u, [u IN [1,2,3] WHERE u>2] AS i $$) AS (result agtype, result2 agtype);
+ result | result2 
+--------+---------
+ [2, 3] | [3]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3] WHERE u>1 | 
u^3] AS u, [u IN [1,2,3] WHERE u>2 | u^3] AS i $$) AS (result agtype, result2 
agtype);
+   result    | result2 
+-------------+---------
+ [8.0, 27.0] | [27.0]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [u IN [1,2,3]]], [u 
IN [u IN [1,2,3]]] $$) AS (result agtype, result2 agtype);
+  result   |  result2  
+-----------+-----------
+ [1, 2, 3] | [1, 2, 3]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [u IN [1,2,3] WHERE 
u>1] WHERE u>2], [u IN [u IN [1,2,3] WHERE u>1] WHERE u>2] $$) AS (result 
agtype, result2 agtype);
+ result | result2 
+--------+---------
+ [3]    | [3]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [u IN [1,2,3] WHERE 
u>1] WHERE u>2 | u^3], [u IN [u IN [1,2,3] WHERE u>1] WHERE u>2 | u^3] $$) AS 
(result agtype, result2 agtype);
+ result | result2 
+--------+---------
+ [27.0] | [27.0]
+(1 row)
+
+SELECT * FROM cypher('list_comprehension', $$ WITH [u IN [1,2,3]] AS u, [u IN 
[1,2,3]] AS i RETURN u, i $$) AS (result agtype, result2 agtype);
+  result   |  result2  
+-----------+-----------
+ [1, 2, 3] | [1, 2, 3]
+(1 row)
+
+-- Should error out
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN range(0, 10, 2)],i 
$$) AS (result agtype, i agtype);
+ERROR:  could not find rte for i
+LINE 1: ..._comprehension', $$ RETURN [i IN range(0, 10, 2)],i $$) AS (...
+                                                             ^
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN range(0, 10, 2) 
WHERE i>5 | i^2], i $$) AS (result agtype, i agtype);
+ERROR:  could not find rte for i
+LINE 1: ...$$ RETURN [i IN range(0, 10, 2) WHERE i>5 | i^2], i $$) AS (...
+                                                             ^
+-- Invalid list comprehension
+SELECT * FROM cypher('list_comprehension', $$ RETURN [1 IN range(0, 10, 2) 
WHERE 2>5] $$) AS (result agtype);
+ERROR:  Syntax error at or near IN
+LINE 1: SELECT * FROM cypher('list_comprehension', $$ RETURN [1 IN r...
+                                                    ^
+SELECT * FROM cypher('list_comprehension', $$ RETURN [1 IN range(0, 10, 2) | 
1] $$) AS (result agtype);
+ERROR:  Syntax error at or near IN
+LINE 1: SELECT * FROM cypher('list_comprehension', $$ RETURN [1 IN r...
+                                                    ^
+SELECT * FROM drop_graph('list_comprehension', true);
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table list_comprehension._ag_label_vertex
+drop cascades to table list_comprehension._ag_label_edge
+drop cascades to table list_comprehension.csm_match
+drop cascades to table list_comprehension.edge
+NOTICE:  graph "list_comprehension" has been dropped
+ drop_graph 
+------------
+ 
+(1 row)
+
diff --git a/regress/sql/list_comprehension.sql 
b/regress/sql/list_comprehension.sql
new file mode 100644
index 00000000..6a33b649
--- /dev/null
+++ b/regress/sql/list_comprehension.sql
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+LOAD 'age';
+SET search_path TO ag_catalog;
+
+SELECT create_graph('list_comprehension');
+
+SELECT * from cypher('list_comprehension', $$ CREATE (u {list: [0, 2, 4, 6, 8, 
10, 12]}) RETURN u $$) AS (result agtype);
+SELECT * from cypher('list_comprehension', $$ CREATE (u {list: [1, 3, 5, 7, 9, 
11, 13]}) RETURN u $$) AS (result agtype);
+SELECT * from cypher('list_comprehension', $$ CREATE (u {list: []}) RETURN u 
$$) AS (result agtype);
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2)] 
$$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2)][2] 
$$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 
2)][1..4] $$) AS (result agtype);
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) 
WHERE u % 3 = 0] $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) 
WHERE u % 3 = 0][2] $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) 
WHERE u % 3 = 0][0..4] $$) AS (result agtype);
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) 
WHERE u % 3 = 0 | u^2 ] $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) 
WHERE u % 3 = 0 | u^2 ][3] $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) 
WHERE u % 3 = 0 | u^2 ][1..5] $$) AS (result agtype);
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) | 
u^2 ] $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) | 
u^2 ][0] $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) | 
u^2 ][0..2] $$) AS (result agtype);
+
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN (u) $$) AS 
(result agtype);
+SELECT * FROM cypher('list_comprehension', $$ MATCH () RETURN [i IN range(0, 
20, 2) WHERE i % 3 = 0 ] $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ MATCH () RETURN [i IN range(0, 
20, 2) WHERE i % 3 = 0 | i^2 ] $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN range(0, 
20, 2) WHERE i % 3 = 0 | i^2 ] $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ MATCH p=() RETURN [i IN range(0, 
20, 2) WHERE i % 3 = 0 | i^2 ] $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ MATCH p=(u) RETURN [i IN 
range(0, 20, 2) WHERE i % 3 = 0 | i^2 ] $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list] 
$$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list 
WHERE i % 3 = 0] $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list 
WHERE i % 3 = 0 | i/3] $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list 
WHERE i % 3 = 0 | i/3][1] $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list 
WHERE i % 3 = 0 | i/3][0..2] $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list 
WHERE i % 3 = 0 | i/3][0..2][1] $$) AS (result agtype);
+
+-- Nested cases
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3]]] $$) 
AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [i IN 
[1,2,3]]]] $$) AS (result agtype);
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE 
i>1]] $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3]] 
WHERE i>1] $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE 
i>1] WHERE i>2] $$) AS (result agtype);
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE 
i>1 | i^2]] $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3]] 
WHERE i>1 | i^2] $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE 
i>1] WHERE i>2 | i^2] $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE 
i>1 | i^2] WHERE i>4] $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE 
i>1 | i^2] WHERE i>4 | i^2] $$) AS (result agtype);
+
+-- List comprehension inside where and property constraints
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list=[i IN 
range(0,12,2)] RETURN u $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list=[i IN 
range(1,13,2)] RETURN u $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list=[i IN 
range(0,12,2) WHERE i>4] RETURN u $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list=[i IN 
range(1,13,2) WHERE i>4 | i^1] RETURN u $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list@>[i IN 
range(0,6,2)] RETURN u $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u) MATCH (v) WHERE 
v.list=[i IN u.list] RETURN v $$) AS (result agtype);
+
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list:[i IN 
range(0,12,2)]}) RETURN u $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list:[i IN 
range(1,13,2)]}) RETURN u $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list:[i IN 
range(0,12,2) WHERE i>4]}) RETURN u $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list:[i IN 
range(1,13,2) WHERE i>4 | i^1]}) RETURN u $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ MATCH (u) MATCH (v {list:[i IN 
u.list]}) RETURN v $$) AS (result agtype);
+
+SELECT * FROM cypher('list_comprehension', $$ CREATE (u {list:[i IN 
range(12,24,2)]}) RETURN u $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ CREATE (u {list:[i IN 
range(0,12,2) WHERE i>4]}) RETURN u $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ CREATE (u {list:[i IN 
range(1,13,2) WHERE i>4 | i^2]}) RETURN u $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ WITH [u IN [1,2,3]] AS a CREATE 
(u {list:a}) RETURN u $$) AS (result agtype);
+
+-- List comprehension in the WITH WHERE clause
+SELECT * FROM cypher('list_comprehension', $$ MATCH(u) WITH u WHERE u.list = 
[u IN [0, 2, 4, 6, 8, 10, 12]] RETURN u $$) AS (u agtype);
+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);
+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);
+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);
+
+SELECT * FROM cypher('list_comprehension', $$ CREATE(u:csm_match {list: 
['abc', 'def', 'ghi']}) $$) AS (u agtype);
+SELECT * FROM cypher('list_comprehension', $$ MATCH(u: csm_match) WITH u WHERE 
[u IN u.list][0] STARTS WITH "ab" RETURN u $$) AS (u agtype);
+
+SELECT * FROM cypher('list_comprehension', $$ WITH [1, 2, 3] AS u WHERE u = [u 
IN [1, 2, 3]] RETURN u $$) AS (u agtype);
+SELECT * FROM cypher('list_comprehension', $$ WITH 1 AS u WHERE u = [u IN [1, 
2, 3]][0] RETURN u $$) AS (u agtype);
+SELECT * FROM cypher('list_comprehension', $$ WITH ['abc', 'defgh'] AS u WHERE 
[v In u][1] STARTS WITH 'de' RETURN u $$) AS (u agtype);
+
+-- List comprehension in UNWIND
+SELECT * FROM cypher('list_comprehension', $$ UNWIND [u IN [1, 2, 3]] AS u 
RETURN u $$) AS (u agtype);
+SELECT * FROM cypher('list_comprehension', $$ UNWIND [u IN [1, 2, 3] WHERE u > 
1 | u*2] AS u RETURN u $$) AS (u agtype);
+
+-- invalid use of aggregation in UNWIND
+SELECT * FROM cypher('list_comprehension', $$ WITH [1, 2, 3] AS u UNWIND 
collect(u) AS v RETURN v $$) AS (u agtype);
+
+-- List comprehension in SET
+SELECT * FROM cypher('list_comprehension', $$ MATCH(u {list: [0, 2, 4, 6, 8, 
10, 12]}) SET u.a = [u IN range(0, 5)] RETURN u $$) AS (u agtype);
+SELECT * FROM cypher('list_comprehension', $$ MATCH(u {list: [0, 2, 4, 6, 8, 
10, 12]}) SET u.a = [u IN []] RETURN u $$) AS (u agtype);
+SELECT * FROM cypher('list_comprehension', $$ MATCH(u {list: [0, 2, 4, 6, 8, 
10, 12]}) SET u += {b: [u IN range(0, 5)]} RETURN u $$) AS (u 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 (u agtype);
+
+-- 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);
+SELECT * FROM cypher('list_comprehension', $$ WITH [m IN [1,2,3]] AS m RETURN 
[m IN [1, 2, 3]], m $$) AS (result agtype, result2 agtype);
+SELECT * FROM cypher('list_comprehension', $$ CREATE n=()-[:edge]->() RETURN 
[n IN nodes(n)] $$) AS (u agtype);
+
+-- Multiple list comprehensions in RETURN and WITH clause
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3]], [u IN 
[1,2,3]] $$) AS (result agtype, result2 agtype);
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3] WHERE u>1], 
[u IN [1,2,3] WHERE u>2] $$) AS (result agtype, result2 agtype);
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3] WHERE u>1 | 
u^3], [u IN [1,2,3] WHERE u>2 | u^3] $$) AS (result agtype, result2 agtype);
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3]] AS u, [u 
IN [1,2,3]] AS i $$) AS (result agtype, result2 agtype);
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3] WHERE u>1] 
AS u, [u IN [1,2,3] WHERE u>2] AS i $$) AS (result agtype, result2 agtype);
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3] WHERE u>1 | 
u^3] AS u, [u IN [1,2,3] WHERE u>2 | u^3] AS i $$) AS (result agtype, result2 
agtype);
+
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [u IN [1,2,3]]], [u 
IN [u IN [1,2,3]]] $$) AS (result agtype, result2 agtype);
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [u IN [1,2,3] WHERE 
u>1] WHERE u>2], [u IN [u IN [1,2,3] WHERE u>1] WHERE u>2] $$) AS (result 
agtype, result2 agtype);
+SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [u IN [1,2,3] WHERE 
u>1] WHERE u>2 | u^3], [u IN [u IN [1,2,3] WHERE u>1] WHERE u>2 | u^3] $$) AS 
(result agtype, result2 agtype);
+
+SELECT * FROM cypher('list_comprehension', $$ WITH [u IN [1,2,3]] AS u, [u IN 
[1,2,3]] AS i RETURN u, i $$) AS (result agtype, result2 agtype);
+
+-- Should error out
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN range(0, 10, 2)],i 
$$) AS (result agtype, i agtype);
+SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN range(0, 10, 2) 
WHERE i>5 | i^2], i $$) AS (result agtype, i agtype);
+
+-- Invalid list comprehension
+SELECT * FROM cypher('list_comprehension', $$ RETURN [1 IN range(0, 10, 2) 
WHERE 2>5] $$) AS (result agtype);
+SELECT * FROM cypher('list_comprehension', $$ RETURN [1 IN range(0, 10, 2) | 
1] $$) AS (result agtype);
+
+SELECT * FROM drop_graph('list_comprehension', true);
\ No newline at end of file
diff --git a/sql/agtype_typecast.sql b/sql/agtype_typecast.sql
index 08e27fb3..5326182f 100644
--- a/sql/agtype_typecast.sql
+++ b/sql/agtype_typecast.sql
@@ -181,7 +181,8 @@ CREATE FUNCTION ag_catalog.age_range(variadic "any")
 PARALLEL SAFE
 AS 'MODULE_PATHNAME';
 
-CREATE FUNCTION ag_catalog.age_unnest(agtype)
+CREATE FUNCTION ag_catalog.age_unnest(agtype,
+                                      list_comprehension boolean = false)
     RETURNS SETOF agtype
 LANGUAGE c
 IMMUTABLE
diff --git a/src/backend/nodes/cypher_outfuncs.c 
b/src/backend/nodes/cypher_outfuncs.c
index 0279e24d..2904503f 100644
--- a/src/backend/nodes/cypher_outfuncs.c
+++ b/src/backend/nodes/cypher_outfuncs.c
@@ -174,6 +174,7 @@ 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_delete ExtensibleNode.
diff --git a/src/backend/parser/cypher_clause.c 
b/src/backend/parser/cypher_clause.c
index 6b3ca3d1..026c8035 100644
--- a/src/backend/parser/cypher_clause.c
+++ b/src/backend/parser/cypher_clause.c
@@ -289,7 +289,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)
-static ParseNamespaceItem
+ParseNamespaceItem
 *transform_cypher_clause_as_subquery(cypher_parsestate *cpstate,
                                      transform_method transform,
                                      cypher_clause *clause,
@@ -397,7 +397,14 @@ Query *transform_cypher_clause(cypher_parsestate *cpstate,
     }
     else if (is_ag_node(self, cypher_unwind))
     {
-        result = transform_cypher_unwind(cpstate, clause);
+        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);
     }
     else if (is_ag_node(self, cypher_call))
     {
@@ -1326,6 +1333,9 @@ 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;
@@ -1354,24 +1364,31 @@ static Query *transform_cypher_unwind(cypher_parsestate 
*cpstate,
         ereport(ERROR,
                 (errcode(ERRCODE_DUPLICATE_ALIAS),
                         errmsg("duplicate variable \"%s\"", 
self->target->name),
-                        parser_errposition((ParseState *) cpstate,
-                                           target_syntax_loc)));
+                        parser_errposition(pstate, target_syntax_loc)));
     }
 
     expr = transform_cypher_expr(cpstate, self->target->val,
                                  EXPR_KIND_SELECT_TARGET);
 
+    if (!has_agg && nodeTag(expr) == T_Aggref)
+    {
+        ereport(ERROR, errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                errmsg("Invalid use of aggregation in this context"),
+                parser_errposition(pstate, self->target->location));
+    }
+
     unwind = makeFuncCall(list_make1(makeString("age_unnest")), NIL,
                           COERCE_SQL_SYNTAX, -1);
 
     old_expr_kind = pstate->p_expr_kind;
     pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
     funcexpr = ParseFuncOrColumn(pstate, unwind->funcname,
-                                 list_make1(expr),
+                                 list_make2(expr, makeBoolConst(is_list_comp, 
false)),
                                  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++,
@@ -1381,6 +1398,7 @@ static Query *transform_cypher_unwind(cypher_parsestate 
*cpstate,
     query->rtable = pstate->p_rtable;
     query->jointree = makeFromExpr(pstate->p_joinlist, NULL);
     query->hasTargetSRFs = pstate->p_hasTargetSRFs;
+    query->hasAggs = pstate->p_hasAggs;
 
     assign_query_collations(pstate, query);
 
@@ -1793,6 +1811,19 @@ cypher_update_information 
*transform_cypher_set_item_list(
         target_item = transform_cypher_item(cpstate, set_item->expr, NULL,
                                             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)
+        {
+            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                    errmsg("Invalid use of aggregation in this context"),
+                    parser_errposition(pstate, set_item->location)));
+        }
+
         target_item->expr = add_volatile_wrapper(target_item->expr);
 
         query->targetList = lappend(query->targetList, target_item);
@@ -2318,9 +2349,13 @@ 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);
-        Assert(rtindex == 1); // rte is the only RangeTblEntry in pstate
+
+        // rte is the only RangeTblEntry in pstate
+        Assert(rtindex == 1);
+
         if (rtindex != 1)
         {
             ereport(ERROR,
@@ -2339,15 +2374,36 @@ static Query 
*transform_cypher_clause_with_where(cypher_parsestate *cpstate,
 
         query->rtable = pstate->p_rtable;
 
-        if (!is_ag_node(self, cypher_match))
+        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))
         {
-            where_qual = transform_cypher_expr(cpstate, where, 
EXPR_KIND_WHERE);
+            List *groupClause = NIL;
+            ListCell *li;
+            query->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+            query->havingQual = where_qual;
 
-            where_qual = coerce_to_boolean(pstate, where_qual, "WHERE");
-        }
+            foreach (li, ((cypher_return *)self)->items)
+            {
+                ResTarget *item = lfirst(li);
 
-        query->jointree = makeFromExpr(pstate->p_joinlist, where_qual);
-        assign_query_collations(pstate, query);
+                groupClause = lappend(groupClause, item->val);
+            }
+            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);
+        }
     }
     else
     {
@@ -2358,6 +2414,8 @@ 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;
 }
 
@@ -2382,9 +2440,7 @@ static Query *transform_cypher_match(cypher_parsestate 
*cpstate,
                                                      (Node *)r, -1);
     }
 
-    return transform_cypher_clause_with_where(
-        cpstate, transform_cypher_match_pattern, clause,
-        match_self->where);
+    return transform_cypher_match_pattern(cpstate, clause);
 }
 
 /*
@@ -3087,7 +3143,36 @@ static void transform_match_pattern(cypher_parsestate 
*cpstate, Query *query,
     }
 
     query->rtable = cpstate->pstate.p_rtable;
-    query->jointree = makeFromExpr(cpstate->pstate.p_joinlist, (Node *)expr);
+
+    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);
+    }
 }
 
 /*
@@ -5454,6 +5539,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;
 
     return query;
 }
@@ -6089,7 +6175,7 @@ static Expr *cypher_create_properties(cypher_parsestate 
*cpstate,
  * This function is similar to transformFromClause() that is called with a
  * single RangeSubselect.
  */
-static ParseNamespaceItem *
+ParseNamespaceItem *
 transform_cypher_clause_as_subquery(cypher_parsestate *cpstate,
                                     transform_method transform,
                                     cypher_clause *clause,
@@ -6111,7 +6197,8 @@ 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_FROM_SUBSELECT ||
+           pstate->p_expr_kind == EXPR_KIND_INSERT_TARGET);
 
     /*
      * As these are all sub queries, if this is just of type NONE, note it as a
@@ -6463,6 +6550,7 @@ 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;
 
diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c
index e2d1ec48..f7e00c6a 100644
--- a/src/backend/parser/cypher_expr.c
+++ b/src/backend/parser/cypher_expr.c
@@ -92,6 +92,8 @@ static ArrayExpr *make_agtype_array_expr(List *args);
 static Node *transform_column_ref_for_indirection(cypher_parsestate *cpstate,
                                                   ColumnRef *cr);
 static bool verify_common_type_coercion(Oid common_type, List *exprs);
+static Node *transform_cypher_list_comprehension(cypher_parsestate *cpstate,
+                                                 cypher_unwind *expr);
 
 /* transform a cypher expression */
 Node *transform_cypher_expr(cypher_parsestate *cpstate, Node *expr,
@@ -168,34 +170,61 @@ static Node 
*transform_cypher_expr_recurse(cypher_parsestate *cpstate,
     case T_CoalesceExpr:
         return transform_CoalesceExpr(cpstate, (CoalesceExpr *) expr);
     case T_ExtensibleNode:
+    {
         if (is_ag_node(expr, cypher_bool_const))
+        {
             return transform_cypher_bool_const(cpstate,
                                                (cypher_bool_const *)expr);
+        }
         if (is_ag_node(expr, cypher_integer_const))
+        {
             return transform_cypher_integer_const(cpstate,
                                                   (cypher_integer_const 
*)expr);
+        }
         if (is_ag_node(expr, cypher_param))
+        {
             return transform_cypher_param(cpstate, (cypher_param *)expr);
+        }
         if (is_ag_node(expr, cypher_map))
+        {
             return transform_cypher_map(cpstate, (cypher_map *)expr);
+        }
         if (is_ag_node(expr, cypher_list))
+        {
             return transform_cypher_list(cpstate, (cypher_list *)expr);
+        }
         if (is_ag_node(expr, cypher_string_match))
+        {
             return transform_cypher_string_match(cpstate,
                                                  (cypher_string_match *)expr);
+        }
         if (is_ag_node(expr, cypher_typecast))
+        {
             return transform_cypher_typecast(cpstate,
                                              (cypher_typecast *)expr);
+        }
         if (is_ag_node(expr, cypher_comparison_aexpr))
+        {
             return transform_cypher_comparison_aexpr_OP(cpstate,
                                              (cypher_comparison_aexpr *)expr);
+        }
         if (is_ag_node(expr, cypher_comparison_boolexpr))
+        {
             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)));
+        
         return NULL;
+    }
     case T_FuncCall:
         return transform_FuncCall(cpstate, (FuncCall *)expr);
     case T_SubLink:
@@ -321,8 +350,26 @@ static Node *transform_ColumnRef(cypher_parsestate 
*cpstate, ColumnRef *cref)
                 Assert(IsA(field1, String));
                 colname = strVal(field1);
 
-                /* Try to identify as an unqualified column */
-                node = colNameToVar(pstate, colname, false, cref->location);
+                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);
+                }
+
                 if (node != NULL)
                 {
                         break;
@@ -1642,6 +1689,9 @@ static Node *transform_SubLink(cypher_parsestate 
*cpstate, SubLink *sublink)
             /* Accept sublink here; caller must throw error if wanted */
 
             break;
+        case EXPR_KIND_JOIN_ON:
+        case EXPR_KIND_JOIN_USING:
+        case EXPR_KIND_FROM_FUNCTION:
         case EXPR_KIND_SELECT_TARGET:
         case EXPR_KIND_FROM_SUBSELECT:
         case EXPR_KIND_WHERE:
@@ -1683,8 +1733,64 @@ static Node *transform_SubLink(cypher_parsestate 
*cpstate, SubLink *sublink)
         sublink->testexpr = NULL;
         sublink->operName = NIL;
     }
+    else if (sublink->subLinkType == EXPR_SUBLINK ||
+            sublink->subLinkType == ARRAY_SUBLINK)
+    {
+        /*
+         * Make sure the subselect delivers a single column (ignoring resjunk
+         * targets).
+         */
+        if (count_nonjunk_tlist_entries(qtree->targetList) != 1)
+        {
+            ereport(ERROR,
+                    (errcode(ERRCODE_SYNTAX_ERROR),
+                     errmsg("subquery must return only one column"),
+                     parser_errposition(pstate, sublink->location)));
+        }
+
+        /*
+         * EXPR and ARRAY need no test expression or combining operator. These
+         * fields should be null already, but make sure.
+         */
+        sublink->testexpr = NULL;
+        sublink->operName = NIL;
+    }
+    else if (sublink->subLinkType == MULTIEXPR_SUBLINK)
+    {
+        /* Same as EXPR case, except no restriction on number of columns */
+        sublink->testexpr = NULL;
+        sublink->operName = NIL;
+    }
     else
         elog(ERROR, "unsupported SubLink type");
 
     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;
+}
\ No newline at end of file
diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y
index f95a3d24..7d8b0266 100644
--- a/src/backend/parser/cypher_gram.y
+++ b/src/backend/parser/cypher_gram.y
@@ -118,6 +118,9 @@
 /* UNWIND clause */
 %type <node> unwind
 
+/* list comprehension */
+%type <node> list_comprehension
+
 /* SET and REMOVE clause */
 %type <node> set set_item remove remove_item
 %type <list> set_item_list remove_item_list
@@ -247,6 +250,18 @@ static bool 
is_A_Expr_a_comparison_operation(cypher_comparison_aexpr *a);
 static Node *build_comparison_expression(Node *left_grammar_node,
                                          Node *right_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,
+                                           Node *where, Node *mapping_expr,
+                                           int var_loc, int expr_loc,
+                                           int where_loc,int mapping_loc);
+
 %}
 %%
 
@@ -1029,6 +1044,8 @@ unwind:
 
             n = make_ag_node(cypher_unwind);
             n->target = res;
+            n->where = NULL;
+            n->collect = NULL;
             $$ = (Node *) n;
         }
 
@@ -2031,6 +2048,52 @@ list:
 
             $$ = (Node *)n;
         }
+    | list_comprehension
+    ;
+
+/*
+ * This grammar rule is generic to some extent. It can
+ * evaluate to either IN operator or list comprehension.
+ * This avoids shift/reduce errors between the two rules.
+ */
+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 (nodeTag(n) == T_ColumnRef)
+            {
+                ColumnRef *cref = (ColumnRef *)n;
+                result = build_list_comprehension_node(cref, $4, NULL, NULL,
+                                                       @2, @4, 0, 0);
+            }
+            else
+            {
+                result = (Node *)makeSimpleA_Expr(AEXPR_IN, "=", n, $4, @3);
+            }
+            $$ = result;
+        }
+    | '[' expr IN expr WHERE expr ']'
+        {
+            $$ = verify_rule_as_list_comprehension($2, $4, $6, NULL,
+                                                   @2, @4, @6, 0);
+        }
+    | '[' expr IN expr '|' expr ']'
+        {
+            $$ = verify_rule_as_list_comprehension($2, $4, NULL, $6,
+                                                   @2, @4, 0, @6);
+        }
+    | '[' expr IN expr WHERE expr '|' expr ']'
+        {
+            $$ = verify_rule_as_list_comprehension($2, $4, $6, $8,
+                                                   @2, @4, @6, @8);
+        }
     ;
 
 expr_case:
@@ -3049,3 +3112,91 @@ static cypher_relationship *build_VLE_relation(List 
*left_arg,
     /* return the VLE relation node */
     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)
+{
+    Node *result = 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
+    {
+        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;
+    Value *val;
+
+    // Extract name from cref
+    val = linitial(cref->fields);
+
+    if (!IsA(val, String))
+    {
+        ereport(ERROR,
+                (errmsg_internal("unexpected Node for cypher_clause")));
+    }
+
+    var_name = val->val.str;
+
+    /*
+     * Build the ResTarget node for the UNWIND variable var_name attached to
+     * expr.
+     */
+    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;
+}
\ No newline at end of file
diff --git a/src/backend/parser/cypher_item.c b/src/backend/parser/cypher_item.c
index 2ce16f84..9ababbc1 100644
--- a/src/backend/parser/cypher_item.c
+++ b/src/backend/parser/cypher_item.c
@@ -27,13 +27,16 @@
 #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,
@@ -41,10 +44,17 @@ 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);
 
@@ -52,6 +62,143 @@ 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)
@@ -68,6 +215,7 @@ 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)
         {
@@ -95,14 +243,47 @@ List *transform_cypher_item_list(cypher_parsestate 
*cpstate, List *item_list,
                 }
             }
         }
-        /* clear the exprHasAgg flag to check transform for an aggregate */
+
+        // 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;
 
-        /* transform the item */
-        te = transform_cypher_item(cpstate, item->val, NULL, expr_kind,
-                                   item->name, 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, -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,
@@ -117,6 +298,58 @@ 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 c41a3547..ad515835 100644
--- a/src/backend/utils/adt/agtype.c
+++ b/src/backend/utils/adt/agtype.c
@@ -11456,6 +11456,7 @@ 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;
@@ -11466,13 +11467,35 @@ Datum age_unnest(PG_FUNCTION_ARGS)
     agtype_value v;
     agtype_iterator_token r;
 
-    // check for null
+    /* 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,
@@ -11529,6 +11552,25 @@ 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 fad0ae91..23d68393 100644
--- a/src/include/nodes/ag_nodes.h
+++ b/src/include/nodes/ag_nodes.h
@@ -104,5 +104,6 @@ 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 79070fb0..a825764b 100644
--- a/src/include/nodes/cypher_nodes.h
+++ b/src/include/nodes/cypher_nodes.h
@@ -117,6 +117,10 @@ typedef struct cypher_unwind
 {
     ExtensibleNode extensible;
     ResTarget *target;
+
+    /* for list comprehension */
+    Node *where;
+    Node *collect;
 } cypher_unwind;
 
 typedef struct cypher_merge
diff --git a/src/include/parser/cypher_clause.h 
b/src/include/parser/cypher_clause.h
index dd554650..6ce76c73 100644
--- a/src/include/parser/cypher_clause.h
+++ b/src/include/parser/cypher_clause.h
@@ -39,4 +39,13 @@ 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 92b6c95f..ba4e7e9a 100644
--- a/src/include/parser/cypher_item.h
+++ b/src/include/parser/cypher_item.h
@@ -26,4 +26,6 @@ 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 d8ed6765..75dcc1c0 100644
--- a/src/include/parser/cypher_parse_node.h
+++ b/src/include/parser/cypher_parse_node.h
@@ -50,6 +50,7 @@ typedef struct cypher_parsestate
      */
     bool exprHasAgg;
     bool p_opt_match;
+    bool p_list_comp;
 } cypher_parsestate;
 
 typedef struct errpos_ecb_state

Reply via email to