You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@age.apache.org by jg...@apache.org on 2023/03/20 20:38:44 UTC

[age] branch PG11 updated (5d751411 -> 1a703285)

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

jgemignani pushed a change to branch PG11
in repository https://gitbox.apache.org/repos/asf/age.git


    from 5d751411 Fix golang driver workflow (#563)
     new 58a46ed8 Updated Readme for drivers folder (#642)
     new 50264274 Implement CI test for jdbc driver (#665)
     new 6a3fa926 Update Go driver CI to run on every PR/commit (#683)
     new 1df55f5b Removed async from function definitions (#680)
     new 5df5d440 Update python driver CI to run on every PR/commit (#684)
     new 0c793704 Barbell graph generation (#648)
     new 73353c5a UPDATED Python Driver ANTLR 4.9.3 -> 4.11.1 (#706)
     new c3331ed7 Modify docker url for JDBC driver tests (#716)
     new 4817bfb5 Fix WITH ignoring WHERE clause (#646)
     new 88c44b9a Implement isEmpty() predicate function (#710)
     new 1b226efc Barbell regress tests (#708)
     new 8d1febe7 Fix cypher function input argument checks (issue #718)
     new 4c8ee36a Fix Issue #693 - server termination from return exists(path) (#721)
     new ef82b9dd Implemented CI test for nodejs driver (#720)
     new 9dbdde99 Update regression tests for cypher_with (#725)
     new 89feda14 Fix issue #733 - create_complete_graph() terminates the server (#734)
     new 08220930 Prevent MATCH from following OPTIONAL MATCH (#740)
     new 68e3b5ae Remove check for scalar agtypes in unwind (#736)
     new 1a703285 Fix property constraints against resolved variables in concurrent MATCH clauses(#701) (#747)

The 19 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .github/workflows/go-driver.yml                    |   8 +-
 .github/workflows/jdbc-driver.yaml                 |  32 ++
 .github/workflows/nodejs-driver.yaml               |  36 ++
 .github/workflows/python-driver.yaml               |   8 +-
 .gitignore                                         |   2 +
 age--1.2.0.sql                                     |  29 +-
 drivers/README                                     |   5 +
 drivers/docker-compose.yml                         |   2 +-
 drivers/jdbc/lib/build.gradle.kts                  |  19 +-
 .../org/apache/age/jdbc/BaseDockerizedTest.java    |   2 +-
 drivers/nodejs/README.md                           |   4 +
 drivers/nodejs/package.json                        |  37 +-
 drivers/nodejs/test/Agtype.test.ts                 |   8 +-
 drivers/nodejs/test/index.test.ts                  |  59 ++-
 drivers/python/age/gen/AgtypeLexer.py              | 153 +++----
 drivers/python/age/gen/AgtypeListener.py           |   3 +-
 drivers/python/age/gen/AgtypeParser.py             | 100 ++---
 drivers/python/age/gen/AgtypeVisitor.py            |   3 +-
 drivers/python/antlr/README.md                     |  14 +-
 drivers/python/requirements.txt                    | Bin 158 -> 160 bytes
 drivers/python/setup.py                            |   2 +-
 regress/expected/analyze.out                       |   5 +
 regress/expected/cypher_match.out                  | 500 ++++++++++++++++++++-
 regress/expected/cypher_merge.out                  |  40 +-
 regress/expected/cypher_unwind.out                 | 155 ++++++-
 regress/expected/cypher_with.out                   | 317 ++++++++++++-
 regress/expected/graph_generation.out              | 119 +++++
 regress/sql/analyze.sql                            |   4 +
 regress/sql/cypher_match.sql                       | 207 +++++++++
 regress/sql/cypher_unwind.sql                      |  92 +++-
 regress/sql/cypher_with.sql                        | 204 ++++++++-
 regress/sql/graph_generation.sql                   |  35 ++
 src/backend/catalog/ag_label.c                     |  15 +
 src/backend/parser/cypher_analyze.c                |   9 +
 src/backend/parser/cypher_clause.c                 | 244 +++++++---
 src/backend/parser/cypher_expr.c                   |  16 +-
 src/backend/utils/adt/agtype.c                     |  95 +++-
 src/backend/utils/graph_generation.c               | 185 +++++++-
 src/backend/utils/load/age_load.c                  |  13 +
 src/include/catalog/ag_label.h                     |   1 +
 40 files changed, 2441 insertions(+), 341 deletions(-)
 create mode 100644 .github/workflows/jdbc-driver.yaml
 create mode 100644 .github/workflows/nodejs-driver.yaml


[age] 09/19: Fix WITH ignoring WHERE clause (#646)

Posted by jg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 4817bfb5a1a2f6dc2a4f9c96ac3349854de09688
Author: Muhammad Taha Naveed <85...@users.noreply.github.com>
AuthorDate: Sat Feb 25 05:52:21 2023 +0500

    Fix WITH ignoring WHERE clause (#646)
    
    * Fix WITH ignoring WHERE clause
    
    - When transforming WITH clause into return clause, WHERE was getting
      ignored because that case wasn't handled in tranform_cypher_clause_with_where.
    - Had to revert to the previous function header of tranform_cypher_clause_with_where
      which had a explicit parameter of where node, because after tranforming cypher_with
      into cypher_return, the wrapper clause now has cypher_return and the where node of
      cypher_with was lost.
    
    * Fix WITH clause segmentation fault
    
    - When transforming columnrefs in WITH clause, it should only check
      referenced Columns in its parsestate or parent parsestate.
    
    - If only expr_kind is WHERE, try to find the columnRef as a transform_entity
      and extract the expr. This will happen in case of (MATCH/WHERE).
    
    * Add regression tests for cypher_with
---
 regress/expected/cypher_with.out   | 353 ++++++++++++++++++++++++++++++++++++-
 regress/sql/cypher_with.sql        | 211 +++++++++++++++++++++-
 src/backend/parser/cypher_clause.c |  57 +++---
 src/backend/parser/cypher_expr.c   |  16 +-
 4 files changed, 582 insertions(+), 55 deletions(-)

diff --git a/regress/expected/cypher_with.out b/regress/expected/cypher_with.out
index 7f5faca3..1166fff3 100644
--- a/regress/expected/cypher_with.out
+++ b/regress/expected/cypher_with.out
@@ -16,8 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+ \! cp -r regress/age_load/data regress/instance/data/age_load
 LOAD 'age';
 SET search_path TO ag_catalog;
+--
+-- Load data
+--
 SELECT create_graph('cypher_with');
 NOTICE:  graph "cypher_with" has been created
  create_graph 
@@ -25,31 +29,366 @@ NOTICE:  graph "cypher_with" has been created
  
 (1 row)
 
+SELECT create_vlabel('cypher_with','Country');
+NOTICE:  VLabel "Country" has been created
+ create_vlabel 
+---------------
+ 
+(1 row)
+
+SELECT load_labels_from_file('cypher_with', 'Country',
+    'age_load/countries.csv');
+ load_labels_from_file 
+-----------------------
+ 
+(1 row)
+
+SELECT create_vlabel('cypher_with','City');
+NOTICE:  VLabel "City" has been created
+ create_vlabel 
+---------------
+ 
+(1 row)
+
+SELECT load_labels_from_file('cypher_with', 'City',
+    'age_load/cities.csv');
+ load_labels_from_file 
+-----------------------
+ 
+(1 row)
+
+SELECT create_elabel('cypher_with','has_city');
+NOTICE:  ELabel "has_city" has been created
+ create_elabel 
+---------------
+ 
+(1 row)
+
+SELECT load_edges_from_file('cypher_with', 'has_city',
+     'age_load/edges.csv');
+ load_edges_from_file 
+----------------------
+ 
+(1 row)
+
+--
+-- Test WITH clause
+--
+SELECT count(*) FROM cypher('cypher_with', $$
+    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'}) 
+    WITH m,b
+    RETURN m,b
+$$) AS (City agtype, Country agtype);
+ count 
+-------
+  2361
+(1 row)
+
+-- WITH/AS
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (b:Country {iso2 : 'AT'}) 
+    WITH b.name AS name, id(b) AS id 
+    RETURN name,id
+$$) AS (Country agtype, Country_id agtype);
+  country  |   country_id    
+-----------+-----------------
+ "Austria" | 844424930131983
+(1 row)
+
 SELECT * FROM cypher('cypher_with', $$
-WITH true AS b
-RETURN b
+    MATCH (m:City {name: 'Zell'})-[]-(b:Country {iso2 : 'AT'})
+    WITH b as country, count(*) AS foaf
+    WHERE foaf > 1      
+    RETURN country.name, foaf
+$$) as (name agtype, foaf agtype);
+   name    | foaf 
+-----------+------
+ "Austria" | 2
+(1 row)
+
+SELECT * FROM cypher('cypher_with', $$
+    WITH true AS b
+    RETURN b
 $$) AS (b bool);
  b 
 ---
  t
 (1 row)
 
+-- WITH/WHERE
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (m:City)-[:has_city]->(b:Country{iso2:'BE'})
+    WITH b,m 
+    WHERE m.name='x'
+    RETURN m.name,b.iso2
+$$) AS ( "m.name" agtype, "b" agtype);
+ m.name | b 
+--------+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (b:Country {iso2 : 'AT'}) 
+    WITH b.name AS name, id(b) AS id
+    WHERE name = 'Austria'
+    RETURN name,id
+$$) AS (Country agtype, Country_id agtype);
+  country  |   country_id    
+-----------+-----------------
+ "Austria" | 844424930131983
+(1 row)
+
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (b:Country {iso2 : 'AT'}) 
+    WITH b.name AS name, id(b) AS id
+    WHERE name = 'Austria' OR name = 'Kosovo'
+    RETURN name,id                             
+$$) AS (Country agtype, Country_id agtype);
+  country  |   country_id    
+-----------+-----------------
+ "Austria" | 844424930131983
+(1 row)
+
+SELECT * FROM cypher('cypher_with', $$
+    MATCH p = (m:City)-[:has_city*1..2]->(b:Country {iso2 : 'AT'}) 
+    WITH p, length(p) AS path_length 
+    WHERE path_length > 1 
+    RETURN p
+$$) AS (pattern agtype);
+ pattern 
+---------
+(0 rows)
+
+-- MATCH/WHERE with WITH/WHERE
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
+    WHERE b.name = 'Austria'
+    WITH m.name AS city,b.name AS country
+    WHERE city = 'Vienna'
+    RETURN city,country
+$$) AS (City agtype, Country agtype);
+   city   |  country  
+----------+-----------
+ "Vienna" | "Austria"
+(1 row)
+
+-- WITH/ORDER BY
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
+    WITH m AS city,b AS country
+    ORDER BY id(m) DESC LIMIT 10
+    RETURN id(city),city
+$$) AS (id agtype, city agtype);
+        id        |                                                                                                                                     city                                                                                                                                      
+------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ 1125899906986495 | {"id": 1125899906986495, "label": "City", "properties": {"id": "143871", "name": "Zell", "__id__": 143871, "latitude": "46.47222222", "state_id": "2057", "longitude": "14.38888889", "country_id": "15", "state_code": "2", "country_code": "AT"}}::vertex
+ 1125899906986492 | {"id": 1125899906986492, "label": "City", "properties": {"id": "143868", "name": "Weißenstein", "__id__": 143868, "latitude": "46.68222222", "state_id": "2057", "longitude": "13.72361111", "country_id": "15", "state_code": "2", "country_code": "AT"}}::vertex
+ 1125899906986491 | {"id": 1125899906986491, "label": "City", "properties": {"id": "143867", "name": "Weißensee", "__id__": 143867, "latitude": "46.71666667", "state_id": "2057", "longitude": "13.3", "country_id": "15", "state_code": "2", "country_code": "AT"}}::vertex
+ 1125899906986490 | {"id": 1125899906986490, "label": "City", "properties": {"id": "143866", "name": "Weitensfeld im Gurktal", "__id__": 143866, "latitude": "46.84861111", "state_id": "2057", "longitude": "14.19166667", "country_id": "15", "state_code": "2", "country_code": "AT"}}::vertex
+ 1125899906986489 | {"id": 1125899906986489, "label": "City", "properties": {"id": "143865", "name": "Velden am Wörther See", "__id__": 143865, "latitude": "46.6125", "state_id": "2057", "longitude": "14.04194444", "country_id": "15", "state_code": "2", "country_code": "AT"}}::vertex
+ 1125899906986488 | {"id": 1125899906986488, "label": "City", "properties": {"id": "143864", "name": "Umberg", "__id__": 143864, "latitude": "46.64833", "state_id": "2057", "longitude": "13.9575", "country_id": "15", "state_code": "2", "country_code": "AT"}}::vertex
+ 1125899906986487 | {"id": 1125899906986487, "label": "City", "properties": {"id": "143863", "name": "Tschachoritsch", "__id__": 143863, "latitude": "46.55352", "state_id": "2057", "longitude": "14.21461", "country_id": "15", "state_code": "2", "country_code": "AT"}}::vertex
+ 1125899906986486 | {"id": 1125899906986486, "label": "City", "properties": {"id": "143862", "name": "Treffen", "__id__": 143862, "latitude": "46.66833333", "state_id": "2057", "longitude": "13.85555556", "country_id": "15", "state_code": "2", "country_code": "AT"}}::vertex
+ 1125899906986484 | {"id": 1125899906986484, "label": "City", "properties": {"id": "143860", "name": "Timenitz", "__id__": 143860, "latitude": "46.68333", "state_id": "2057", "longitude": "14.41667", "country_id": "15", "state_code": "2", "country_code": "AT"}}::vertex
+ 1125899906986483 | {"id": 1125899906986483, "label": "City", "properties": {"id": "143859", "name": "Techelsberg", "__id__": 143859, "latitude": "46.65861111", "state_id": "2057", "longitude": "14.10194444", "country_id": "15", "state_code": "2", "country_code": "AT"}}::vertex
+(10 rows)
+
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (m:City) 
+    WITH m AS city
+    ORDER BY id(m) ASC LIMIT 10
+    RETURN id(city),city.name
+$$) AS (id agtype, names agtype);
+        id        |         names         
+------------------+-----------------------
+ 1125899906842625 | "Andorra la Vella"
+ 1125899906842626 | "Arinsal"
+ 1125899906842627 | "Canillo"
+ 1125899906842628 | "El Tarter"
+ 1125899906842629 | "Encamp"
+ 1125899906842630 | "Ordino"
+ 1125899906842631 | "Pas de la Casa"
+ 1125899906842632 | "Sant Julià de Lòria"
+ 1125899906842633 | "la Massana"
+ 1125899906842634 | "les Escaldes"
+(10 rows)
+
+-- WITH/ORDER BY/DESC/WHERE
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
+    WITH m AS city,b AS country
+    ORDER BY id(m) DESC LIMIT 10
+    WHERE city.name = 'Zell' OR city.name = 'Umberg'
+    RETURN id(city),city.name,country.name
+$$) AS (id agtype, city agtype, country agtype);
+        id        |   city   |  country  
+------------------+----------+-----------
+ 1125899906986495 | "Zell"   | "Austria"
+ 1125899906986488 | "Umberg" | "Austria"
+(2 rows)
+
+-- multiple WITH clauses
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
+    WITH m AS city,b AS country     
+    WITH city LIMIT 10
+    RETURN city.name
+$$) AS (city agtype);
+        city         
+---------------------
+ "Andau"
+ "Antau"
+ "Apetlon"
+ "Bad Sauerbrunn"
+ "Bad Tatzmannsdorf"
+ "Badersdorf"
+ "Bernstein"
+ "Bocksdorf"
+ "Breitenbrunn"
+ "Bruckneudorf"
+(10 rows)
+
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
+    WITH m AS city,b AS country
+    ORDER BY id(m) DESC LIMIT 10
+    WITH city
+    WHERE city.name = 'Zell'
+    RETURN id(city),city.name
+$$) AS (id agtype, city agtype);
+        id        |  city  
+------------------+--------
+ 1125899906986495 | "Zell"
+(1 row)
+
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
+    WITH m AS city,b AS country
+    WHERE country.name = 'Austria'
+    WITH city
+    ORDER BY id(city) DESC
+    WHERE city.name = 'Zell'
+    RETURN id(city),city.name
+$$) AS (id agtype, city agtype);
+        id        |  city  
+------------------+--------
+ 1125899906986495 | "Zell"
+ 1125899906846495 | "Zell"
+(2 rows)
+
 -- Expression item must be aliased.
 SELECT * FROM cypher('cypher_with', $$
-WITH 1 + 1
-RETURN i
+    WITH 1 + 1
+    RETURN i
 $$) AS (i int);
 ERROR:  expression item must be aliased
-LINE 2: WITH 1 + 1
-             ^
+LINE 2:     WITH 1 + 1
+                 ^
 HINT:  Items can be aliased by using AS.
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
+    WITH id(m)
+    RETURN m
+$$) AS (id agtype, city agtype);
+ERROR:  expression item must be aliased
+LINE 3:     WITH id(m)
+                 ^
+HINT:  Items can be aliased by using AS.
+-- Reference undefined variable in WITH clause (should error out)
+SELECT count(*) FROM cypher('cypher_with', $$
+    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'}) 
+    WITH m  
+    RETURN m,b
+$$) AS (City agtype, Country agtype);
+ERROR:  could not find rte for b
+LINE 4:     RETURN m,b
+                     ^
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
+    WITH m AS city,b AS country
+    WHERE country.name = 'Austria'
+    WITH city
+    WHERE city.name = 'Zell'
+    RETURN id(city),country.name
+$$) AS (id agtype, country agtype);
+ERROR:  could not find rte for country
+LINE 7:     RETURN id(city),country.name
+                            ^
+-- Clean up
 SELECT drop_graph('cypher_with', true);
-NOTICE:  drop cascades to 2 other objects
+NOTICE:  drop cascades to 5 other objects
 DETAIL:  drop cascades to table cypher_with._ag_label_vertex
 drop cascades to table cypher_with._ag_label_edge
+drop cascades to table cypher_with."Country"
+drop cascades to table cypher_with."City"
+drop cascades to table cypher_with.has_city
 NOTICE:  graph "cypher_with" has been dropped
  drop_graph 
 ------------
  
 (1 row)
 
+-- Issue 329 (should error out)
+SELECT create_graph('graph');
+NOTICE:  graph "graph" has been created
+ create_graph 
+--------------
+ 
+(1 row)
+
+SELECT * FROM cypher('graph', $$
+    CREATE (a:A)-[:incs]->(:C), (a)-[:incs]->(:C)
+    RETURN a
+$$) AS (n agtype);
+                                n                                
+-----------------------------------------------------------------
+ {"id": 844424930131969, "label": "A", "properties": {}}::vertex
+(1 row)
+
+SELECT * FROM cypher('graph', $$
+    MATCH (a:A) 
+    WHERE ID(a)=0 
+    WITH a 
+    OPTIONAL MATCH (a)-[:incs]->(c)-[d:incs]-() 
+    WITH a,c,COUNT(d) AS deps 
+    WHERE deps<=1 
+    RETURN c,d
+$$) AS (n agtype, d agtype);
+ERROR:  could not find rte for d
+LINE 8:     RETURN c,d
+                     ^
+-- Issue 396 (should error out)
+SELECT * FROM cypher('graph',$$
+     CREATE (v),(u),(w),
+            (v)-[:hasFriend]->(u),
+            (u)-[:hasFriend]->(w)
+$$) as (a agtype);
+ a 
+---
+(0 rows)
+
+SELECT * FROM cypher('graph',$$
+      MATCH p=(v)-[*1..2]->(u) 
+      WITH p,length(p) AS path_length 
+      RETURN v,path_length
+$$) as (a agtype,b agtype);
+ERROR:  could not find rte for v
+LINE 4:       RETURN v,path_length
+                     ^
+-- Clean up
+SELECT drop_graph('graph', true);
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to table graph._ag_label_vertex
+drop cascades to table graph._ag_label_edge
+drop cascades to table graph."A"
+drop cascades to table graph.incs
+drop cascades to table graph."C"
+drop cascades to table graph."hasFriend"
+NOTICE:  graph "graph" has been dropped
+ drop_graph 
+------------
+ 
+(1 row)
+
+--
+-- End of test
+--
diff --git a/regress/sql/cypher_with.sql b/regress/sql/cypher_with.sql
index 623487b4..95b18c50 100644
--- a/regress/sql/cypher_with.sql
+++ b/regress/sql/cypher_with.sql
@@ -17,20 +17,223 @@
  * under the License.
  */
 
+ \! cp -r regress/age_load/data regress/instance/data/age_load
+
 LOAD 'age';
 SET search_path TO ag_catalog;
 
+--
+-- Load data
+--
 SELECT create_graph('cypher_with');
 
+SELECT create_vlabel('cypher_with','Country');
+SELECT load_labels_from_file('cypher_with', 'Country',
+    'age_load/countries.csv');
+
+SELECT create_vlabel('cypher_with','City');
+SELECT load_labels_from_file('cypher_with', 'City',
+    'age_load/cities.csv');
+
+SELECT create_elabel('cypher_with','has_city');
+SELECT load_edges_from_file('cypher_with', 'has_city',
+     'age_load/edges.csv');
+
+--
+-- Test WITH clause
+--
+
+SELECT count(*) FROM cypher('cypher_with', $$
+    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'}) 
+    WITH m,b
+    RETURN m,b
+$$) AS (City agtype, Country agtype);
+
+-- WITH/AS
+
 SELECT * FROM cypher('cypher_with', $$
-WITH true AS b
-RETURN b
+    MATCH (b:Country {iso2 : 'AT'}) 
+    WITH b.name AS name, id(b) AS id 
+    RETURN name,id
+$$) AS (Country agtype, Country_id agtype);
+
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (m:City {name: 'Zell'})-[]-(b:Country {iso2 : 'AT'})
+    WITH b as country, count(*) AS foaf
+    WHERE foaf > 1      
+    RETURN country.name, foaf
+$$) as (name agtype, foaf agtype);
+
+SELECT * FROM cypher('cypher_with', $$
+    WITH true AS b
+    RETURN b
 $$) AS (b bool);
 
+-- WITH/WHERE
+
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (m:City)-[:has_city]->(b:Country{iso2:'BE'})
+    WITH b,m 
+    WHERE m.name='x'
+    RETURN m.name,b.iso2
+$$) AS ( "m.name" agtype, "b" agtype);
+
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (b:Country {iso2 : 'AT'}) 
+    WITH b.name AS name, id(b) AS id
+    WHERE name = 'Austria'
+    RETURN name,id
+$$) AS (Country agtype, Country_id agtype);
+
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (b:Country {iso2 : 'AT'}) 
+    WITH b.name AS name, id(b) AS id
+    WHERE name = 'Austria' OR name = 'Kosovo'
+    RETURN name,id                             
+$$) AS (Country agtype, Country_id agtype);
+
+SELECT * FROM cypher('cypher_with', $$
+    MATCH p = (m:City)-[:has_city*1..2]->(b:Country {iso2 : 'AT'}) 
+    WITH p, length(p) AS path_length 
+    WHERE path_length > 1 
+    RETURN p
+$$) AS (pattern agtype);
+
+-- MATCH/WHERE with WITH/WHERE
+
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
+    WHERE b.name = 'Austria'
+    WITH m.name AS city,b.name AS country
+    WHERE city = 'Vienna'
+    RETURN city,country
+$$) AS (City agtype, Country agtype);
+
+-- WITH/ORDER BY
+
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
+    WITH m AS city,b AS country
+    ORDER BY id(m) DESC LIMIT 10
+    RETURN id(city),city
+$$) AS (id agtype, city agtype);
+
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (m:City) 
+    WITH m AS city
+    ORDER BY id(m) ASC LIMIT 10
+    RETURN id(city),city.name
+$$) AS (id agtype, names agtype);
+
+-- WITH/ORDER BY/DESC/WHERE
+
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
+    WITH m AS city,b AS country
+    ORDER BY id(m) DESC LIMIT 10
+    WHERE city.name = 'Zell' OR city.name = 'Umberg'
+    RETURN id(city),city.name,country.name
+$$) AS (id agtype, city agtype, country agtype);
+
+-- multiple WITH clauses
+
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
+    WITH m AS city,b AS country     
+    WITH city LIMIT 10
+    RETURN city.name
+$$) AS (city agtype);
+
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
+    WITH m AS city,b AS country
+    ORDER BY id(m) DESC LIMIT 10
+    WITH city
+    WHERE city.name = 'Zell'
+    RETURN id(city),city.name
+$$) AS (id agtype, city agtype);
+
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
+    WITH m AS city,b AS country
+    WHERE country.name = 'Austria'
+    WITH city
+    ORDER BY id(city) DESC
+    WHERE city.name = 'Zell'
+    RETURN id(city),city.name
+$$) AS (id agtype, city agtype);
+
 -- Expression item must be aliased.
+
 SELECT * FROM cypher('cypher_with', $$
-WITH 1 + 1
-RETURN i
+    WITH 1 + 1
+    RETURN i
 $$) AS (i int);
 
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
+    WITH id(m)
+    RETURN m
+$$) AS (id agtype, city agtype);
+
+-- Reference undefined variable in WITH clause (should error out)
+
+SELECT count(*) FROM cypher('cypher_with', $$
+    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'}) 
+    WITH m  
+    RETURN m,b
+$$) AS (City agtype, Country agtype);
+
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
+    WITH m AS city,b AS country
+    WHERE country.name = 'Austria'
+    WITH city
+    WHERE city.name = 'Zell'
+    RETURN id(city),country.name
+$$) AS (id agtype, country agtype);
+
+-- Clean up
+
 SELECT drop_graph('cypher_with', true);
+
+-- Issue 329 (should error out)
+
+SELECT create_graph('graph');
+
+SELECT * FROM cypher('graph', $$
+    CREATE (a:A)-[:incs]->(:C), (a)-[:incs]->(:C)
+    RETURN a
+$$) AS (n agtype);
+
+SELECT * FROM cypher('graph', $$
+    MATCH (a:A) 
+    WHERE ID(a)=0 
+    WITH a 
+    OPTIONAL MATCH (a)-[:incs]->(c)-[d:incs]-() 
+    WITH a,c,COUNT(d) AS deps 
+    WHERE deps<=1 
+    RETURN c,d
+$$) AS (n agtype, d agtype);
+
+-- Issue 396 (should error out)
+
+SELECT * FROM cypher('graph',$$
+     CREATE (v),(u),(w),
+            (v)-[:hasFriend]->(u),
+            (u)-[:hasFriend]->(w)
+$$) as (a agtype);
+
+SELECT * FROM cypher('graph',$$
+      MATCH p=(v)-[*1..2]->(u) 
+      WITH p,length(p) AS path_length 
+      RETURN v,path_length
+$$) as (a agtype,b agtype);
+
+-- Clean up
+
+SELECT drop_graph('graph', true);
+
+--
+-- End of test
+--
diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c
index 46655111..380eef92 100644
--- a/src/backend/parser/cypher_clause.c
+++ b/src/backend/parser/cypher_clause.c
@@ -114,7 +114,8 @@ static Query *transform_cypher_with(cypher_parsestate *cpstate,
                                     cypher_clause *clause);
 static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate,
                                                  transform_method transform,
-                                                 cypher_clause *clause);
+                                                 cypher_clause *clause,
+                                                 Node *where);
 // match clause
 static Query *transform_cypher_match(cypher_parsestate *cpstate,
                                      cypher_clause *clause);
@@ -1067,7 +1068,7 @@ static Query * transform_cypher_call_stmt(cypher_parsestate *cpstate,
 
         return transform_cypher_clause_with_where(cpstate,
                                                   transform_cypher_call_subquery,
-                                                  clause);
+                                                  clause, self->where);
     }
 
     return NULL;
@@ -2194,7 +2195,7 @@ static Query *transform_cypher_with(cypher_parsestate *cpstate,
     wrapper->prev = clause->prev;
 
     return transform_cypher_clause_with_where(cpstate, transform_cypher_return,
-                                              wrapper);
+                                              wrapper, self->where);
 }
 
 static bool match_check_valid_label(cypher_match *match,
@@ -2254,28 +2255,14 @@ static bool match_check_valid_label(cypher_match *match,
 
     return true;
 }
-
 static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate,
                                                  transform_method transform,
-                                                 cypher_clause *clause)
+                                                 cypher_clause *clause, Node *where)
 {
     ParseState *pstate = (ParseState *)cpstate;
     Query *query;
     Node *self = clause->self;
-    cypher_match *match_self;
-    cypher_call *call_self;
-    Node *where;
-
-    if (is_ag_node(self, cypher_call))
-    {
-        call_self = (cypher_call*) clause->self;
-        where = call_self->where;
-    }
-    else
-    {
-        match_self = (cypher_match*) clause->self;
-        where = match_self->where;
-    }
+    Node *where_qual = NULL;
 
     if (where)
     {
@@ -2290,32 +2277,27 @@ static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate,
         rtindex = list_length(pstate->p_rtable);
         Assert(rtindex == 1); // rte is the only RangeTblEntry in pstate
 
+        /*
+         * add all the target entries in rte to the current target list to pass
+         * all the variables that are introduced in the previous clause to the
+         * next clause
+         */
         query->targetList = expandRelAttrs(pstate, rte, rtindex, 0, -1);
 
         markTargetListOrigins(pstate, query->targetList);
 
         query->rtable = pstate->p_rtable;
 
-        if (is_ag_node(clause->self, cypher_call))
+        if (!is_ag_node(self, cypher_match))
         {
-            cypher_call *call = (cypher_call *)clause->self;
-
-            if (call->where != NULL)
-            {
-                Expr *where_qual = NULL;
-
-                where_qual = (Expr *)transform_cypher_expr(cpstate, call->where,
-                                                           EXPR_KIND_WHERE);
+            where_qual = transform_cypher_expr(cpstate, where,
+                                                        EXPR_KIND_WHERE);
 
-                where_qual = (Expr *)coerce_to_boolean(pstate, (Node *)where_qual,
-                                               "WHERE");
-                query->jointree = makeFromExpr(pstate->p_joinlist, (Node *)where_qual);
-            }
-        }
-        else
-        {
-            query->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+            where_qual = coerce_to_boolean(pstate, where_qual,
+                                            "WHERE");
         }
+        
+        query->jointree = makeFromExpr(pstate->p_joinlist, where_qual);
         assign_query_collations(pstate, query);
     }
     else
@@ -2352,7 +2334,8 @@ static Query *transform_cypher_match(cypher_parsestate *cpstate,
     }
 
     return transform_cypher_clause_with_where(
-        cpstate, transform_cypher_match_pattern, clause);
+        cpstate, transform_cypher_match_pattern, clause, 
+        match_self->where);
 }
 
 /*
diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c
index 8a276ffe..f0bc9c8c 100644
--- a/src/backend/parser/cypher_expr.c
+++ b/src/backend/parser/cypher_expr.c
@@ -318,16 +318,18 @@ static Node *transform_ColumnRef(cypher_parsestate *cpstate, ColumnRef *cref)
                 }
 
                 /*
-                 * Try to find the columnRef as a transform_entity and extract
-                 * the expr.
+                 * If expr_kind is WHERE, Try to find the columnRef as a
+                 * transform_entity and extract the expr.
                  */
-                te = find_variable(cpstate, colname) ;
-                if (te != NULL && te->expr != NULL)
+                if (pstate->p_expr_kind == EXPR_KIND_WHERE)
                 {
-                    node = (Node *)te->expr;
-                    break;
+                    te = find_variable(cpstate, colname) ;
+                    if (te != NULL && te->expr != NULL)
+                    {
+                        node = (Node *)te->expr;
+                        break;
+                    }
                 }
-
                 /*
                  * Not known as a column of any range-table entry.
                  * Try to find the name as a relation.  Note that only


[age] 02/19: Implement CI test for jdbc driver (#665)

Posted by jg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 50264274343466c64db02ba07fef956368047ff9
Author: Muhammad Taha Naveed <85...@users.noreply.github.com>
AuthorDate: Thu Feb 16 22:13:15 2023 +0500

    Implement CI test for jdbc driver (#665)
    
    - This CI will only run whenever a PR or commit touches files
      drivers/jdbc.
    
    - Added logger to display the result of tests i.e. passing,
      failing etc.
---
 .github/workflows/jdbc-driver.yaml | 32 ++++++++++++++++++++++++++++++++
 drivers/jdbc/lib/build.gradle.kts  | 19 ++++++++++++++++++-
 2 files changed, 50 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/jdbc-driver.yaml b/.github/workflows/jdbc-driver.yaml
new file mode 100644
index 00000000..0946bdc3
--- /dev/null
+++ b/.github/workflows/jdbc-driver.yaml
@@ -0,0 +1,32 @@
+name: JDBC Driver Tests
+
+on:
+  push:
+    branches: [ "master", "PG11" ]
+      
+  pull_request:
+    branches: [ "master", "PG11" ]
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    
+    strategy:
+      matrix:
+        distributions: ['zulu', 'temurin', 'microsoft']
+
+    defaults:
+      run:
+        working-directory: drivers/jdbc
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Set up Java
+      uses: actions/setup-java@v3
+      with:
+        distribution: ${{ matrix.distributions }}
+        java-version: '17'
+
+    - name: Build and Test
+      run: gradle build
\ No newline at end of file
diff --git a/drivers/jdbc/lib/build.gradle.kts b/drivers/jdbc/lib/build.gradle.kts
index 86af61ac..5e2d0f27 100644
--- a/drivers/jdbc/lib/build.gradle.kts
+++ b/drivers/jdbc/lib/build.gradle.kts
@@ -17,6 +17,9 @@
  * under the License.
  */
 
+import org.gradle.api.tasks.testing.logging.TestExceptionFormat
+import org.gradle.api.tasks.testing.logging.TestLogEvent
+
 plugins {
     `java-library`
     antlr
@@ -36,6 +39,9 @@ dependencies {
 
     testImplementation("org.testcontainers:testcontainers:1.15.3")
     testImplementation("org.postgresql:postgresql:42.2.20")
+
+    testImplementation("org.slf4j:slf4j-api:1.7.5")
+    testImplementation("org.slf4j:slf4j-simple:1.7.5")
 }
 
 tasks.generateGrammarSource {
@@ -50,5 +56,16 @@ tasks.generateGrammarSource {
 }
 
 tasks.test {
-    useJUnitPlatform()
+    useJUnitPlatform();
+    testLogging {
+        // set options for log level LIFECYCLE
+        events(TestLogEvent.FAILED,
+            TestLogEvent.PASSED,
+            TestLogEvent.SKIPPED,
+            TestLogEvent.STANDARD_OUT)
+        exceptionFormat = TestExceptionFormat.FULL
+        showExceptions  = true
+        showCauses = true
+        showStackTraces  = true
+    }
 }


[age] 10/19: Implement isEmpty() predicate function (#710)

Posted by jg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 88c44b9a1845092510abaae5053b01b46ad076e8
Author: Muhammad Taha Naveed <85...@users.noreply.github.com>
AuthorDate: Tue Feb 28 01:23:31 2023 +0500

    Implement isEmpty() predicate function (#710)
    
    - Added isEmpty() predicate function to check if a List, map or string is
      empty.
    
    - Added regression tests for isEmpty() function.
---
 age--1.2.0.sql                    |   8 ++
 regress/expected/cypher_match.out | 160 ++++++++++++++++++++++++++++++++++++++
 regress/sql/cypher_match.sql      |  97 +++++++++++++++++++++++
 src/backend/utils/adt/agtype.c    |  81 +++++++++++++++++++
 4 files changed, 346 insertions(+)

diff --git a/age--1.2.0.sql b/age--1.2.0.sql
index e34b3c97..e50f1a62 100644
--- a/age--1.2.0.sql
+++ b/age--1.2.0.sql
@@ -3551,6 +3551,14 @@ STABLE
 PARALLEL SAFE
 AS 'MODULE_PATHNAME';
 
+CREATE FUNCTION ag_catalog.age_isempty(agtype)
+RETURNS boolean
+LANGUAGE c
+IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
 CREATE FUNCTION ag_catalog.age_label(agtype)
 RETURNS agtype
 LANGUAGE c
diff --git a/regress/expected/cypher_match.out b/regress/expected/cypher_match.out
index 6ff9a3c6..9ec4112d 100644
--- a/regress/expected/cypher_match.out
+++ b/regress/expected/cypher_match.out
@@ -816,6 +816,166 @@ AS (u agtype);
 ERROR:  syntax error at or near ")"
 LINE 2:  $$MATCH (u) WHERE EXISTS(u) RETURN u$$)
                                    ^
+--
+-- MATCH with WHERE isEmpty(property)
+--
+SELECT create_graph('for_isEmpty');
+NOTICE:  graph "for_isEmpty" has been created
+ create_graph 
+--------------
+ 
+(1 row)
+
+-- Create vertices
+SELECT * FROM cypher('for_isEmpty',
+ $$CREATE (u:for_pred {id:1, type: "empty", list: [], map: {}, string: ""}),
+		  (v:for_pred {id:2, type: "filled", list: [1], map: {a:1}, string: "a"}),
+		  (w:for_pred)$$)
+AS (a agtype);
+ a 
+---
+(0 rows)
+
+-- Match vertices with empty properties
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE isEmpty(u.list) RETURN properties(u) $$)
+AS (u agtype);
+                                u                                
+-----------------------------------------------------------------
+ {"id": 1, "map": {}, "list": [], "type": "empty", "string": ""}
+(1 row)
+
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE isEmpty(u.map) RETURN properties(u) $$)
+AS (u agtype);
+                                u                                
+-----------------------------------------------------------------
+ {"id": 1, "map": {}, "list": [], "type": "empty", "string": ""}
+(1 row)
+
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE isEmpty(u.string) RETURN properties(u) $$)
+AS (u agtype);
+                                u                                
+-----------------------------------------------------------------
+ {"id": 1, "map": {}, "list": [], "type": "empty", "string": ""}
+(1 row)
+
+-- Match vertices with non-empty properties
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE NOT isEmpty(u.list) RETURN properties(u) $$)
+AS (u agtype);
+                                    u                                     
+--------------------------------------------------------------------------
+ {"id": 2, "map": {"a": 1}, "list": [1], "type": "filled", "string": "a"}
+(1 row)
+
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE NOT isEmpty(u.map) RETURN properties(u) $$)
+AS (u agtype);
+                                    u                                     
+--------------------------------------------------------------------------
+ {"id": 2, "map": {"a": 1}, "list": [1], "type": "filled", "string": "a"}
+(1 row)
+
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE NOT isEmpty(u.string) RETURN properties(u) $$)
+AS (u agtype);
+                                    u                                     
+--------------------------------------------------------------------------
+ {"id": 2, "map": {"a": 1}, "list": [1], "type": "filled", "string": "a"}
+(1 row)
+
+-- Match vertices with no properties
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE isEmpty(properties(u)) RETURN properties(u) $$)
+AS (u agtype);
+ u  
+----
+ {}
+(1 row)
+
+-- Match vertices with properties
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE NOT isEmpty(properties(u)) RETURN properties(u) $$)
+AS (u agtype);
+                                    u                                     
+--------------------------------------------------------------------------
+ {"id": 1, "map": {}, "list": [], "type": "empty", "string": ""}
+ {"id": 2, "map": {"a": 1}, "list": [1], "type": "filled", "string": "a"}
+(2 rows)
+
+-- Match vertices with null property (should return nothing since WHERE null)
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE isEmpty(u.tree) RETURN properties(u) $$)
+AS (u agtype);
+ u 
+---
+(0 rows)
+
+-- Match and Return bool
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE isEmpty(u.list) RETURN isEmpty(u.list), u.type $$)
+AS (b agtype, type agtype);
+  b   |  type   
+------+---------
+ true | "empty"
+(1 row)
+
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE NOT isEmpty(u.list) RETURN isEmpty(u.list), u.type $$)
+AS (b agtype, type agtype);
+   b   |   type   
+-------+----------
+ false | "filled"
+(1 row)
+
+-- Return null on null
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) RETURN isEmpty(u.tree) $$)
+AS (b agtype);
+ b 
+---
+ 
+ 
+ 
+(3 rows)
+
+-- Should give an error
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE isEmpty(u) RETURN properties(u) $$)
+AS (u agtype);
+ERROR:  isEmpty() unsupported argument, expected a List, Map, or String
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE isEmpty(1) RETURN properties(u) $$)
+AS (u agtype);
+ERROR:  isEmpty() unsupported argument, expected a List, Map, or String
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE isEmpty(1,2,3) RETURN properties(u) $$)
+AS (u agtype);
+ERROR:  function ag_catalog.age_isempty(agtype, agtype, agtype) does not exist
+LINE 2:  $$MATCH (u:for_pred) WHERE isEmpty(1,2,3) RETURN properties...
+                                           ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE isEmpty() RETURN properties(u) $$)
+AS (u agtype);
+ERROR:  function ag_catalog.age_isempty() does not exist
+LINE 2:  $$MATCH (u:for_pred) WHERE isEmpty() RETURN properties(u) $...
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+-- clean up
+SELECT drop_graph('for_isEmpty', true);
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table "for_isEmpty"._ag_label_vertex
+drop cascades to table "for_isEmpty"._ag_label_edge
+drop cascades to table "for_isEmpty".for_pred
+NOTICE:  graph "for_isEmpty" has been dropped
+ drop_graph 
+------------
+ 
+(1 row)
+
 --
 --Distinct
 --
diff --git a/regress/sql/cypher_match.sql b/regress/sql/cypher_match.sql
index c4c48bf7..aeed9f38 100644
--- a/regress/sql/cypher_match.sql
+++ b/regress/sql/cypher_match.sql
@@ -415,6 +415,103 @@ SELECT * FROM cypher('cypher_match',
  $$MATCH (u) WHERE EXISTS(u) RETURN u$$)
 AS (u agtype);
 
+--
+-- MATCH with WHERE isEmpty(property)
+--
+
+SELECT create_graph('for_isEmpty');
+
+-- Create vertices
+
+SELECT * FROM cypher('for_isEmpty',
+ $$CREATE (u:for_pred {id:1, type: "empty", list: [], map: {}, string: ""}),
+		  (v:for_pred {id:2, type: "filled", list: [1], map: {a:1}, string: "a"}),
+		  (w:for_pred)$$)
+AS (a agtype);
+
+-- Match vertices with empty properties
+
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE isEmpty(u.list) RETURN properties(u) $$)
+AS (u agtype);
+
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE isEmpty(u.map) RETURN properties(u) $$)
+AS (u agtype);
+
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE isEmpty(u.string) RETURN properties(u) $$)
+AS (u agtype);
+
+-- Match vertices with non-empty properties
+
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE NOT isEmpty(u.list) RETURN properties(u) $$)
+AS (u agtype);
+
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE NOT isEmpty(u.map) RETURN properties(u) $$)
+AS (u agtype);
+
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE NOT isEmpty(u.string) RETURN properties(u) $$)
+AS (u agtype);
+
+-- Match vertices with no properties
+
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE isEmpty(properties(u)) RETURN properties(u) $$)
+AS (u agtype);
+
+-- Match vertices with properties
+
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE NOT isEmpty(properties(u)) RETURN properties(u) $$)
+AS (u agtype);
+
+-- Match vertices with null property (should return nothing since WHERE null)
+
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE isEmpty(u.tree) RETURN properties(u) $$)
+AS (u agtype);
+
+-- Match and Return bool
+
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE isEmpty(u.list) RETURN isEmpty(u.list), u.type $$)
+AS (b agtype, type agtype);
+
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE NOT isEmpty(u.list) RETURN isEmpty(u.list), u.type $$)
+AS (b agtype, type agtype);
+
+-- Return null on null
+
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) RETURN isEmpty(u.tree) $$)
+AS (b agtype);
+
+-- Should give an error
+
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE isEmpty(u) RETURN properties(u) $$)
+AS (u agtype);
+
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE isEmpty(1) RETURN properties(u) $$)
+AS (u agtype);
+
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE isEmpty(1,2,3) RETURN properties(u) $$)
+AS (u agtype);
+
+SELECT * FROM cypher('for_isEmpty',
+ $$MATCH (u:for_pred) WHERE isEmpty() RETURN properties(u) $$)
+AS (u agtype);
+
+-- clean up
+SELECT drop_graph('for_isEmpty', true);
+
 --
 --Distinct
 --
diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c
index 22bf462c..ed4bf4d4 100644
--- a/src/backend/utils/adt/agtype.c
+++ b/src/backend/utils/adt/agtype.c
@@ -5508,6 +5508,87 @@ Datum age_exists(PG_FUNCTION_ARGS)
     PG_RETURN_BOOL(true);
 }
 
+PG_FUNCTION_INFO_V1(age_isempty);
+/*
+ * Executor function for isEmpty(property).
+ */
+
+Datum age_isempty(PG_FUNCTION_ARGS)
+{
+    Datum *args;
+    Datum arg;
+    bool *nulls;
+    Oid *types;
+    char *string = NULL;
+    Oid type;
+    int64 result;
+
+    /* extract argument values */
+    extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls);
+
+    /*
+     * isEmpty() supports cstring, text, or the agtype string or list input
+     */
+    arg = args[0];
+    type = types[0];
+
+    if (type == CSTRINGOID)
+    {
+        string = DatumGetCString(arg);
+        result = strlen(string);
+    }
+    else if (type == TEXTOID)
+    {
+        string = text_to_cstring(DatumGetTextPP(arg));
+        result = strlen(string);
+    }
+    else if (type == AGTYPEOID)
+    {
+        agtype *agt_arg;
+
+        /* get the agtype argument */
+        agt_arg = DATUM_GET_AGTYPE_P(arg);
+
+        if (AGT_ROOT_IS_SCALAR(agt_arg))
+        {
+            agtype_value *agtv_value;
+
+            agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0);
+
+            if (agtv_value->type == AGTV_STRING)
+            {
+                result = agtv_value->val.string.len;
+            }
+            else
+            {
+                ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                        errmsg("isEmpty() unsupported argument, expected a List, Map, or String")));
+            }
+        }
+        else if (AGT_ROOT_IS_ARRAY(agt_arg))
+        {
+            result = AGT_ROOT_COUNT(agt_arg);
+        }
+        else if (AGT_ROOT_IS_OBJECT(agt_arg))
+        {
+            result = AGT_ROOT_COUNT(agt_arg);
+        }
+        else
+        {
+            ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                            errmsg("isEmpty() unsupported argument, expected a List, Map, or String")));
+        }
+    }
+    else
+    {
+        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                        errmsg("isEmpty() unsupported argument, expected a List, Map, or String")));
+    }
+
+    /* build the result */
+    PG_RETURN_BOOL(result == 0);
+}
+
 PG_FUNCTION_INFO_V1(age_label);
 /*
  * Executor function for label(edge/vertex).


[age] 04/19: Removed async from function definitions (#680)

Posted by jg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 1df55f5bb99d0bac3e823316b739ca7ba2c5dc27
Author: Fahad Zaheer <52...@users.noreply.github.com>
AuthorDate: Fri Feb 17 00:41:00 2023 +0500

    Removed async from function definitions (#680)
    
    Found async function without any await expressions in NodeJS Driver. Async keyword is used in Asynchronous programming and for the functions that wait for some responses. These functions dont need to be Asynchronous.
---
 drivers/nodejs/test/Agtype.test.ts | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/nodejs/test/Agtype.test.ts b/drivers/nodejs/test/Agtype.test.ts
index 27f5f4e4..5ae26b54 100644
--- a/drivers/nodejs/test/Agtype.test.ts
+++ b/drivers/nodejs/test/Agtype.test.ts
@@ -20,7 +20,7 @@
 import { AGTypeParse } from '../src'
 
 describe('Parsing', () => {
-  it('Vertex', async () => {
+  it('Vertex', () => {
     expect(
       AGTypeParse('{"id": 844424930131969, "label": "Part", "properties": {"part_num": "123", "number": 3141592653589793, "float": 3.141592653589793}}::vertex')
     ).toStrictEqual(new Map<string, any>(Object.entries({
@@ -34,7 +34,7 @@ describe('Parsing', () => {
     })))
   })
 
-  it('Edge', async () => {
+  it('Edge', () => {
     expect(
       AGTypeParse('{"id": 1125899906842625, "label": "used_by", "end_id": 844424930131970, "start_id": 844424930131969, "properties": {"quantity": 1}}::edge')
     ).toStrictEqual(new Map(Object.entries({
@@ -46,7 +46,7 @@ describe('Parsing', () => {
     })))
   })
 
-  it('Path', async () => {
+  it('Path', () => {
     expect(
       AGTypeParse('[{"id": 844424930131969, "label": "Part", "properties": {"part_num": "123"}}::vertex, {"id": 1125899906842625, "label": "used_by", "end_id": 844424930131970, "start_id": 844424930131969, "properties": {"quantity": 1}}::edge, {"id": 844424930131970, "label": "Part", "properties": {"part_num": "345"}}::vertex]::path')
     ).toStrictEqual([
@@ -70,7 +70,7 @@ describe('Parsing', () => {
     ])
   })
 
-  it('Null Properties', async () => {
+  it('Null Properties', () => {
     expect(
       AGTypeParse('{"id": 1688849860263937, "label": "car", "properties": {}}::vertex')
     ).toStrictEqual(new Map<string, any>(Object.entries({


[age] 15/19: Update regression tests for cypher_with (#725)

Posted by jg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 9dbdde99ba326a9b469a606cb773e0b766330df4
Author: Muhammad Taha Naveed <mn...@seecs.edu.pk>
AuthorDate: Fri Mar 3 23:41:55 2023 +0500

    Update regression tests for cypher_with (#725)
    
    - Used a small dataset for regression tests.
---
 regress/expected/cypher_with.out | 408 ++++++++++++++++++---------------------
 regress/sql/cypher_with.sql      | 207 ++++++++++----------
 2 files changed, 286 insertions(+), 329 deletions(-)

diff --git a/regress/expected/cypher_with.out b/regress/expected/cypher_with.out
index 1166fff3..e5f82aa2 100644
--- a/regress/expected/cypher_with.out
+++ b/regress/expected/cypher_with.out
@@ -16,7 +16,6 @@
  * specific language governing permissions and limitations
  * under the License.
  */
- \! cp -r regress/age_load/data regress/instance/data/age_load
 LOAD 'age';
 SET search_path TO ag_catalog;
 --
@@ -29,82 +28,71 @@ NOTICE:  graph "cypher_with" has been created
  
 (1 row)
 
-SELECT create_vlabel('cypher_with','Country');
-NOTICE:  VLabel "Country" has been created
- create_vlabel 
----------------
- 
-(1 row)
-
-SELECT load_labels_from_file('cypher_with', 'Country',
-    'age_load/countries.csv');
- load_labels_from_file 
------------------------
- 
-(1 row)
-
-SELECT create_vlabel('cypher_with','City');
-NOTICE:  VLabel "City" has been created
- create_vlabel 
----------------
- 
-(1 row)
-
-SELECT load_labels_from_file('cypher_with', 'City',
-    'age_load/cities.csv');
- load_labels_from_file 
------------------------
- 
-(1 row)
-
-SELECT create_elabel('cypher_with','has_city');
-NOTICE:  ELabel "has_city" has been created
- create_elabel 
----------------
- 
-(1 row)
-
-SELECT load_edges_from_file('cypher_with', 'has_city',
-     'age_load/edges.csv');
- load_edges_from_file 
-----------------------
- 
-(1 row)
+SELECT * FROM cypher('cypher_with', $$
+    CREATE (andres {name : 'Andres', age : 36}),
+    (caesar {name : 'Caesar', age : 25}),
+    (bossman {name : 'Bossman', age : 55}),
+    (david {name : 'David', age : 35}),
+    (george {name : 'George', age : 37}),
+    (andres)-[:BLOCKS]->(caesar),
+    (andres)-[:KNOWS]->(bossman),
+    (caesar)-[:KNOWS]->(george),
+    (bossman)-[:BLOCKS]->(david),
+    (bossman)-[:KNOWS]->(george),
+    (david)-[:KNOWS]->(andres)
+$$) as (a agtype);
+ a 
+---
+(0 rows)
 
 --
 -- Test WITH clause
 --
-SELECT count(*) FROM cypher('cypher_with', $$
-    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'}) 
-    WITH m,b
-    RETURN m,b
-$$) AS (City agtype, Country agtype);
- count 
--------
-  2361
-(1 row)
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (n)-[e]->(m) 
+    WITH n,e,m
+    RETURN n,e,m
+$$) AS (N1 agtype, edge agtype, N2 agtype);
+                                             n1                                             |                                                            edge                                                            |                                             n2                                             
+--------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------
+ {"id": 281474976710657, "label": "", "properties": {"age": 36, "name": "Andres"}}::vertex  | {"id": 844424930131969, "label": "BLOCKS", "end_id": 281474976710658, "start_id": 281474976710657, "properties": {}}::edge | {"id": 281474976710658, "label": "", "properties": {"age": 25, "name": "Caesar"}}::vertex
+ {"id": 281474976710659, "label": "", "properties": {"age": 55, "name": "Bossman"}}::vertex | {"id": 844424930131970, "label": "BLOCKS", "end_id": 281474976710660, "start_id": 281474976710659, "properties": {}}::edge | {"id": 281474976710660, "label": "", "properties": {"age": 35, "name": "David"}}::vertex
+ {"id": 281474976710657, "label": "", "properties": {"age": 36, "name": "Andres"}}::vertex  | {"id": 1125899906842625, "label": "KNOWS", "end_id": 281474976710659, "start_id": 281474976710657, "properties": {}}::edge | {"id": 281474976710659, "label": "", "properties": {"age": 55, "name": "Bossman"}}::vertex
+ {"id": 281474976710658, "label": "", "properties": {"age": 25, "name": "Caesar"}}::vertex  | {"id": 1125899906842626, "label": "KNOWS", "end_id": 281474976710661, "start_id": 281474976710658, "properties": {}}::edge | {"id": 281474976710661, "label": "", "properties": {"age": 37, "name": "George"}}::vertex
+ {"id": 281474976710659, "label": "", "properties": {"age": 55, "name": "Bossman"}}::vertex | {"id": 1125899906842627, "label": "KNOWS", "end_id": 281474976710661, "start_id": 281474976710659, "properties": {}}::edge | {"id": 281474976710661, "label": "", "properties": {"age": 37, "name": "George"}}::vertex
+ {"id": 281474976710660, "label": "", "properties": {"age": 35, "name": "David"}}::vertex   | {"id": 1125899906842628, "label": "KNOWS", "end_id": 281474976710657, "start_id": 281474976710660, "properties": {}}::edge | {"id": 281474976710657, "label": "", "properties": {"age": 36, "name": "Andres"}}::vertex
+(6 rows)
 
 -- WITH/AS
 SELECT * FROM cypher('cypher_with', $$
-    MATCH (b:Country {iso2 : 'AT'}) 
-    WITH b.name AS name, id(b) AS id 
-    RETURN name,id
-$$) AS (Country agtype, Country_id agtype);
-  country  |   country_id    
------------+-----------------
- "Austria" | 844424930131983
-(1 row)
+    MATCH (n)-[e]->(m)
+    WITH n.name AS n1, e as edge, m.name as n2
+    RETURN n1,label(edge),n2
+$$) AS (start_node agtype,edge agtype, end_node agtype);
+ start_node |   edge   | end_node  
+------------+----------+-----------
+ "Andres"   | "BLOCKS" | "Caesar"
+ "Bossman"  | "BLOCKS" | "David"
+ "Andres"   | "KNOWS"  | "Bossman"
+ "Caesar"   | "KNOWS"  | "George"
+ "Bossman"  | "KNOWS"  | "George"
+ "David"    | "KNOWS"  | "Andres"
+(6 rows)
 
-SELECT * FROM cypher('cypher_with', $$
-    MATCH (m:City {name: 'Zell'})-[]-(b:Country {iso2 : 'AT'})
-    WITH b as country, count(*) AS foaf
-    WHERE foaf > 1      
-    RETURN country.name, foaf
-$$) as (name agtype, foaf agtype);
-   name    | foaf 
------------+------
- "Austria" | 2
-(1 row)
+SELECT * FROM cypher('cypher_with',$$
+    MATCH (person)-[r]->(otherPerson)
+    WITH *, type(r) AS connectionType
+    RETURN person.name, connectionType, otherPerson.name
+$$) AS (start_node agtype, connection agtype, end_node agtype);
+ start_node | connection | end_node  
+------------+------------+-----------
+ "Andres"   | "BLOCKS"   | "Caesar"
+ "Bossman"  | "BLOCKS"   | "David"
+ "Andres"   | "KNOWS"    | "Bossman"
+ "Caesar"   | "KNOWS"    | "George"
+ "Bossman"  | "KNOWS"    | "George"
+ "David"    | "KNOWS"    | "Andres"
+(6 rows)
 
 SELECT * FROM cypher('cypher_with', $$
     WITH true AS b
@@ -117,162 +105,139 @@ $$) AS (b bool);
 
 -- WITH/WHERE
 SELECT * FROM cypher('cypher_with', $$
-    MATCH (m:City)-[:has_city]->(b:Country{iso2:'BE'})
-    WITH b,m 
-    WHERE m.name='x'
-    RETURN m.name,b.iso2
-$$) AS ( "m.name" agtype, "b" agtype);
- m.name | b 
---------+---
-(0 rows)
-
-SELECT * FROM cypher('cypher_with', $$
-    MATCH (b:Country {iso2 : 'AT'}) 
-    WITH b.name AS name, id(b) AS id
-    WHERE name = 'Austria'
-    RETURN name,id
-$$) AS (Country agtype, Country_id agtype);
-  country  |   country_id    
------------+-----------------
- "Austria" | 844424930131983
+MATCH (george {name: 'George'})<-[]-(otherPerson)
+    WITH otherPerson, toUpper(otherPerson.name) AS upperCaseName
+    WHERE upperCaseName STARTS WITH 'C'
+    RETURN otherPerson.name
+$$) as (name agtype);
+   name   
+----------
+ "Caesar"
 (1 row)
 
 SELECT * FROM cypher('cypher_with', $$
-    MATCH (b:Country {iso2 : 'AT'}) 
-    WITH b.name AS name, id(b) AS id
-    WHERE name = 'Austria' OR name = 'Kosovo'
-    RETURN name,id                             
-$$) AS (Country agtype, Country_id agtype);
-  country  |   country_id    
------------+-----------------
- "Austria" | 844424930131983
+	MATCH (david {name: 'David'})-[]-(otherPerson)-[]->()
+	WITH otherPerson, count(*) AS foaf
+	WHERE foaf > 1
+	RETURN otherPerson.name
+$$) as (name agtype);
+   name   
+----------
+ "Andres"
 (1 row)
 
 SELECT * FROM cypher('cypher_with', $$
-    MATCH p = (m:City)-[:has_city*1..2]->(b:Country {iso2 : 'AT'}) 
+    MATCH p = (m)-[*1..2]->(b) 
     WITH p, length(p) AS path_length 
     WHERE path_length > 1 
     RETURN p
 $$) AS (pattern agtype);
- pattern 
----------
-(0 rows)
+                                                                                                                                                                                                                                                                                             pattern                                                                                                                                                                                                         [...]
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ [...]
+ [{"id": 281474976710657, "label": "_ag_label_vertex", "properties": {"age": 36, "name": "Andres"}}::vertex, {"id": 1125899906842625, "label": "KNOWS", "end_id": 281474976710659, "start_id": 281474976710657, "properties": {}}::edge, {"id": 281474976710659, "label": "_ag_label_vertex", "properties": {"age": 55, "name": "Bossman"}}::vertex, {"id": 1125899906842627, "label": "KNOWS", "end_id": 281474976710661, "start_id": 281474976710659, "properties": {}}::edge, {"id": 281474976710661, "la [...]
+ [{"id": 281474976710657, "label": "_ag_label_vertex", "properties": {"age": 36, "name": "Andres"}}::vertex, {"id": 1125899906842625, "label": "KNOWS", "end_id": 281474976710659, "start_id": 281474976710657, "properties": {}}::edge, {"id": 281474976710659, "label": "_ag_label_vertex", "properties": {"age": 55, "name": "Bossman"}}::vertex, {"id": 844424930131970, "label": "BLOCKS", "end_id": 281474976710660, "start_id": 281474976710659, "properties": {}}::edge, {"id": 281474976710660, "la [...]
+ [{"id": 281474976710657, "label": "_ag_label_vertex", "properties": {"age": 36, "name": "Andres"}}::vertex, {"id": 844424930131969, "label": "BLOCKS", "end_id": 281474976710658, "start_id": 281474976710657, "properties": {}}::edge, {"id": 281474976710658, "label": "_ag_label_vertex", "properties": {"age": 25, "name": "Caesar"}}::vertex, {"id": 1125899906842626, "label": "KNOWS", "end_id": 281474976710661, "start_id": 281474976710658, "properties": {}}::edge, {"id": 281474976710661, "lab [...]
+ [{"id": 281474976710659, "label": "_ag_label_vertex", "properties": {"age": 55, "name": "Bossman"}}::vertex, {"id": 844424930131970, "label": "BLOCKS", "end_id": 281474976710660, "start_id": 281474976710659, "properties": {}}::edge, {"id": 281474976710660, "label": "_ag_label_vertex", "properties": {"age": 35, "name": "David"}}::vertex, {"id": 1125899906842628, "label": "KNOWS", "end_id": 281474976710657, "start_id": 281474976710660, "properties": {}}::edge, {"id": 281474976710657, "lab [...]
+ [{"id": 281474976710660, "label": "_ag_label_vertex", "properties": {"age": 35, "name": "David"}}::vertex, {"id": 1125899906842628, "label": "KNOWS", "end_id": 281474976710657, "start_id": 281474976710660, "properties": {}}::edge, {"id": 281474976710657, "label": "_ag_label_vertex", "properties": {"age": 36, "name": "Andres"}}::vertex, {"id": 1125899906842625, "label": "KNOWS", "end_id": 281474976710659, "start_id": 281474976710657, "properties": {}}::edge, {"id": 281474976710659, "labe [...]
+ [{"id": 281474976710660, "label": "_ag_label_vertex", "properties": {"age": 35, "name": "David"}}::vertex, {"id": 1125899906842628, "label": "KNOWS", "end_id": 281474976710657, "start_id": 281474976710660, "properties": {}}::edge, {"id": 281474976710657, "label": "_ag_label_vertex", "properties": {"age": 36, "name": "Andres"}}::vertex, {"id": 844424930131969, "label": "BLOCKS", "end_id": 281474976710658, "start_id": 281474976710657, "properties": {}}::edge, {"id": 281474976710658, "labe [...]
+(6 rows)
 
 -- MATCH/WHERE with WITH/WHERE
 SELECT * FROM cypher('cypher_with', $$
-    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
-    WHERE b.name = 'Austria'
-    WITH m.name AS city,b.name AS country
-    WHERE city = 'Vienna'
-    RETURN city,country
-$$) AS (City agtype, Country agtype);
-   city   |  country  
-----------+-----------
- "Vienna" | "Austria"
+    MATCH (m)-[e]->(b)
+    WHERE label(e) = 'KNOWS'
+    WITH *
+    WHERE m.name = 'Andres'
+    RETURN m.name,label(e),b.name
+$$) AS (N1 agtype, edge agtype, N2 agtype);
+    n1    |  edge   |    n2     
+----------+---------+-----------
+ "Andres" | "KNOWS" | "Bossman"
 (1 row)
 
 -- WITH/ORDER BY
 SELECT * FROM cypher('cypher_with', $$
-    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
-    WITH m AS city,b AS country
-    ORDER BY id(m) DESC LIMIT 10
-    RETURN id(city),city
-$$) AS (id agtype, city agtype);
-        id        |                                                                                                                                     city                                                                                                                                      
-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- 1125899906986495 | {"id": 1125899906986495, "label": "City", "properties": {"id": "143871", "name": "Zell", "__id__": 143871, "latitude": "46.47222222", "state_id": "2057", "longitude": "14.38888889", "country_id": "15", "state_code": "2", "country_code": "AT"}}::vertex
- 1125899906986492 | {"id": 1125899906986492, "label": "City", "properties": {"id": "143868", "name": "Weißenstein", "__id__": 143868, "latitude": "46.68222222", "state_id": "2057", "longitude": "13.72361111", "country_id": "15", "state_code": "2", "country_code": "AT"}}::vertex
- 1125899906986491 | {"id": 1125899906986491, "label": "City", "properties": {"id": "143867", "name": "Weißensee", "__id__": 143867, "latitude": "46.71666667", "state_id": "2057", "longitude": "13.3", "country_id": "15", "state_code": "2", "country_code": "AT"}}::vertex
- 1125899906986490 | {"id": 1125899906986490, "label": "City", "properties": {"id": "143866", "name": "Weitensfeld im Gurktal", "__id__": 143866, "latitude": "46.84861111", "state_id": "2057", "longitude": "14.19166667", "country_id": "15", "state_code": "2", "country_code": "AT"}}::vertex
- 1125899906986489 | {"id": 1125899906986489, "label": "City", "properties": {"id": "143865", "name": "Velden am Wörther See", "__id__": 143865, "latitude": "46.6125", "state_id": "2057", "longitude": "14.04194444", "country_id": "15", "state_code": "2", "country_code": "AT"}}::vertex
- 1125899906986488 | {"id": 1125899906986488, "label": "City", "properties": {"id": "143864", "name": "Umberg", "__id__": 143864, "latitude": "46.64833", "state_id": "2057", "longitude": "13.9575", "country_id": "15", "state_code": "2", "country_code": "AT"}}::vertex
- 1125899906986487 | {"id": 1125899906986487, "label": "City", "properties": {"id": "143863", "name": "Tschachoritsch", "__id__": 143863, "latitude": "46.55352", "state_id": "2057", "longitude": "14.21461", "country_id": "15", "state_code": "2", "country_code": "AT"}}::vertex
- 1125899906986486 | {"id": 1125899906986486, "label": "City", "properties": {"id": "143862", "name": "Treffen", "__id__": 143862, "latitude": "46.66833333", "state_id": "2057", "longitude": "13.85555556", "country_id": "15", "state_code": "2", "country_code": "AT"}}::vertex
- 1125899906986484 | {"id": 1125899906986484, "label": "City", "properties": {"id": "143860", "name": "Timenitz", "__id__": 143860, "latitude": "46.68333", "state_id": "2057", "longitude": "14.41667", "country_id": "15", "state_code": "2", "country_code": "AT"}}::vertex
- 1125899906986483 | {"id": 1125899906986483, "label": "City", "properties": {"id": "143859", "name": "Techelsberg", "__id__": 143859, "latitude": "46.65861111", "state_id": "2057", "longitude": "14.10194444", "country_id": "15", "state_code": "2", "country_code": "AT"}}::vertex
-(10 rows)
+    MATCH (n)
+    WITH n
+    ORDER BY id(n)
+    RETURN n
+$$) as (name agtype);
+                                            name                                            
+--------------------------------------------------------------------------------------------
+ {"id": 281474976710657, "label": "", "properties": {"age": 36, "name": "Andres"}}::vertex
+ {"id": 281474976710658, "label": "", "properties": {"age": 25, "name": "Caesar"}}::vertex
+ {"id": 281474976710659, "label": "", "properties": {"age": 55, "name": "Bossman"}}::vertex
+ {"id": 281474976710660, "label": "", "properties": {"age": 35, "name": "David"}}::vertex
+ {"id": 281474976710661, "label": "", "properties": {"age": 37, "name": "George"}}::vertex
+(5 rows)
 
+-- WITH/ORDER BY/DESC
 SELECT * FROM cypher('cypher_with', $$
-    MATCH (m:City) 
-    WITH m AS city
-    ORDER BY id(m) ASC LIMIT 10
-    RETURN id(city),city.name
-$$) AS (id agtype, names agtype);
-        id        |         names         
-------------------+-----------------------
- 1125899906842625 | "Andorra la Vella"
- 1125899906842626 | "Arinsal"
- 1125899906842627 | "Canillo"
- 1125899906842628 | "El Tarter"
- 1125899906842629 | "Encamp"
- 1125899906842630 | "Ordino"
- 1125899906842631 | "Pas de la Casa"
- 1125899906842632 | "Sant Julià de Lòria"
- 1125899906842633 | "la Massana"
- 1125899906842634 | "les Escaldes"
-(10 rows)
+    MATCH (n)
+    WITH n
+    ORDER BY n.name DESC LIMIT 3
+    RETURN collect(n.name)
+$$) as (names agtype);
+             names             
+-------------------------------
+ ["George", "David", "Caesar"]
+(1 row)
 
--- WITH/ORDER BY/DESC/WHERE
 SELECT * FROM cypher('cypher_with', $$
-    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
-    WITH m AS city,b AS country
-    ORDER BY id(m) DESC LIMIT 10
-    WHERE city.name = 'Zell' OR city.name = 'Umberg'
-    RETURN id(city),city.name,country.name
-$$) AS (id agtype, city agtype, country agtype);
-        id        |   city   |  country  
-------------------+----------+-----------
- 1125899906986495 | "Zell"   | "Austria"
- 1125899906986488 | "Umberg" | "Austria"
+    MATCH (n {name: 'Andres'})-[]-(m)
+    WITH m
+    ORDER BY m.name DESC LIMIT 1
+    MATCH (m)-[]-(o)
+    RETURN o.name ORDER BY o.name
+$$) as (name agtype);
+   name    
+-----------
+ "Andres"
+ "Bossman"
 (2 rows)
 
 -- multiple WITH clauses
 SELECT * FROM cypher('cypher_with', $$
-    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
-    WITH m AS city,b AS country     
-    WITH city LIMIT 10
-    RETURN city.name
-$$) AS (city agtype);
-        city         
----------------------
- "Andau"
- "Antau"
- "Apetlon"
- "Bad Sauerbrunn"
- "Bad Tatzmannsdorf"
- "Badersdorf"
- "Bernstein"
- "Bocksdorf"
- "Breitenbrunn"
- "Bruckneudorf"
-(10 rows)
+    MATCH (n)-[e]->(m)
+    WITH n, e, m
+    WHERE label(e) = 'KNOWS'
+    WITH n.name as n1, label(e) as edge, m.name as n2
+    WHERE n1 = 'Andres'
+    RETURN n1,edge,n2
+$$) AS (N1 agtype, edge agtype, N2 agtype);
+    n1    |  edge   |    n2     
+----------+---------+-----------
+ "Andres" | "KNOWS" | "Bossman"
+(1 row)
 
 SELECT * FROM cypher('cypher_with', $$
-    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
-    WITH m AS city,b AS country
-    ORDER BY id(m) DESC LIMIT 10
-    WITH city
-    WHERE city.name = 'Zell'
-    RETURN id(city),city.name
-$$) AS (id agtype, city agtype);
-        id        |  city  
-------------------+--------
- 1125899906986495 | "Zell"
-(1 row)
+    UNWIND [1, 2, 3, 4, 5, 6] AS x
+    WITH x
+    WHERE x > 2
+    WITH x
+    LIMIT 5
+    RETURN x
+$$) as (name agtype);
+ name 
+------
+ 3
+ 4
+ 5
+ 6
+(4 rows)
 
 SELECT * FROM cypher('cypher_with', $$
-    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
-    WITH m AS city,b AS country
-    WHERE country.name = 'Austria'
-    WITH city
-    ORDER BY id(city) DESC
-    WHERE city.name = 'Zell'
-    RETURN id(city),city.name
-$$) AS (id agtype, city agtype);
-        id        |  city  
-------------------+--------
- 1125899906986495 | "Zell"
- 1125899906846495 | "Zell"
+    MATCH (m)-[]->(b)
+    WITH m,b
+    ORDER BY id(m) DESC LIMIT 5
+    WITH m as start_node, b as end_node
+    WHERE end_node.name = 'George'
+    RETURN id(start_node),start_node.name,id(end_node),end_node.name
+$$) AS (id1 agtype, name1 agtype, id2 agtype, name2 agtype);
+       id1       |   name1   |       id2       |  name2   
+-----------------+-----------+-----------------+----------
+ 281474976710659 | "Bossman" | 281474976710661 | "George"
+ 281474976710658 | "Caesar"  | 281474976710661 | "George"
 (2 rows)
 
 -- Expression item must be aliased.
@@ -285,42 +250,41 @@ LINE 2:     WITH 1 + 1
                  ^
 HINT:  Items can be aliased by using AS.
 SELECT * FROM cypher('cypher_with', $$
-    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
+    MATCH (m)-[]->(b)
     WITH id(m)
     RETURN m
-$$) AS (id agtype, city agtype);
+$$) AS (id agtype);
 ERROR:  expression item must be aliased
 LINE 3:     WITH id(m)
                  ^
 HINT:  Items can be aliased by using AS.
 -- Reference undefined variable in WITH clause (should error out)
 SELECT count(*) FROM cypher('cypher_with', $$
-    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'}) 
+    MATCH (m)-[]->(b)
     WITH m  
     RETURN m,b
-$$) AS (City agtype, Country agtype);
+$$) AS (a agtype, b agtype);
 ERROR:  could not find rte for b
 LINE 4:     RETURN m,b
                      ^
 SELECT * FROM cypher('cypher_with', $$
-    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
-    WITH m AS city,b AS country
-    WHERE country.name = 'Austria'
-    WITH city
-    WHERE city.name = 'Zell'
-    RETURN id(city),country.name
-$$) AS (id agtype, country agtype);
-ERROR:  could not find rte for country
-LINE 7:     RETURN id(city),country.name
-                            ^
+    MATCH (m)-[]->(b)
+    WITH m AS start_node,b AS end_node
+    WHERE start_node.name = 'Andres'
+    WITH start_node
+    WHERE start_node.name = 'George'
+    RETURN id(start_node),end_node.name
+$$) AS (id agtype, node agtype);
+ERROR:  could not find rte for end_node
+LINE 7:     RETURN id(start_node),end_node.name
+                                  ^
 -- Clean up
 SELECT drop_graph('cypher_with', true);
-NOTICE:  drop cascades to 5 other objects
+NOTICE:  drop cascades to 4 other objects
 DETAIL:  drop cascades to table cypher_with._ag_label_vertex
 drop cascades to table cypher_with._ag_label_edge
-drop cascades to table cypher_with."Country"
-drop cascades to table cypher_with."City"
-drop cascades to table cypher_with.has_city
+drop cascades to table cypher_with."BLOCKS"
+drop cascades to table cypher_with."KNOWS"
 NOTICE:  graph "cypher_with" has been dropped
  drop_graph 
 ------------
@@ -358,22 +322,22 @@ LINE 8:     RETURN c,d
                      ^
 -- Issue 396 (should error out)
 SELECT * FROM cypher('graph',$$
-     CREATE (v),(u),(w),
-            (v)-[:hasFriend]->(u),
-            (u)-[:hasFriend]->(w)
+    CREATE (v),(u),(w),
+        (v)-[:hasFriend]->(u),
+        (u)-[:hasFriend]->(w)
 $$) as (a agtype);
  a 
 ---
 (0 rows)
 
 SELECT * FROM cypher('graph',$$
-      MATCH p=(v)-[*1..2]->(u) 
-      WITH p,length(p) AS path_length 
-      RETURN v,path_length
+    MATCH p=(v)-[*1..2]->(u) 
+    WITH p,length(p) AS path_length 
+    RETURN v,path_length
 $$) as (a agtype,b agtype);
 ERROR:  could not find rte for v
-LINE 4:       RETURN v,path_length
-                     ^
+LINE 4:     RETURN v,path_length
+                   ^
 -- Clean up
 SELECT drop_graph('graph', true);
 NOTICE:  drop cascades to 6 other objects
diff --git a/regress/sql/cypher_with.sql b/regress/sql/cypher_with.sql
index 95b18c50..93413f2c 100644
--- a/regress/sql/cypher_with.sql
+++ b/regress/sql/cypher_with.sql
@@ -17,8 +17,6 @@
  * under the License.
  */
 
- \! cp -r regress/age_load/data regress/instance/data/age_load
-
 LOAD 'age';
 SET search_path TO ag_catalog;
 
@@ -27,42 +25,43 @@ SET search_path TO ag_catalog;
 --
 SELECT create_graph('cypher_with');
 
-SELECT create_vlabel('cypher_with','Country');
-SELECT load_labels_from_file('cypher_with', 'Country',
-    'age_load/countries.csv');
-
-SELECT create_vlabel('cypher_with','City');
-SELECT load_labels_from_file('cypher_with', 'City',
-    'age_load/cities.csv');
-
-SELECT create_elabel('cypher_with','has_city');
-SELECT load_edges_from_file('cypher_with', 'has_city',
-     'age_load/edges.csv');
+SELECT * FROM cypher('cypher_with', $$
+    CREATE (andres {name : 'Andres', age : 36}),
+    (caesar {name : 'Caesar', age : 25}),
+    (bossman {name : 'Bossman', age : 55}),
+    (david {name : 'David', age : 35}),
+    (george {name : 'George', age : 37}),
+    (andres)-[:BLOCKS]->(caesar),
+    (andres)-[:KNOWS]->(bossman),
+    (caesar)-[:KNOWS]->(george),
+    (bossman)-[:BLOCKS]->(david),
+    (bossman)-[:KNOWS]->(george),
+    (david)-[:KNOWS]->(andres)
+$$) as (a agtype);
 
 --
 -- Test WITH clause
 --
 
-SELECT count(*) FROM cypher('cypher_with', $$
-    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'}) 
-    WITH m,b
-    RETURN m,b
-$$) AS (City agtype, Country agtype);
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (n)-[e]->(m) 
+    WITH n,e,m
+    RETURN n,e,m
+$$) AS (N1 agtype, edge agtype, N2 agtype);
 
 -- WITH/AS
 
 SELECT * FROM cypher('cypher_with', $$
-    MATCH (b:Country {iso2 : 'AT'}) 
-    WITH b.name AS name, id(b) AS id 
-    RETURN name,id
-$$) AS (Country agtype, Country_id agtype);
+    MATCH (n)-[e]->(m)
+    WITH n.name AS n1, e as edge, m.name as n2
+    RETURN n1,label(edge),n2
+$$) AS (start_node agtype,edge agtype, end_node agtype);
 
-SELECT * FROM cypher('cypher_with', $$
-    MATCH (m:City {name: 'Zell'})-[]-(b:Country {iso2 : 'AT'})
-    WITH b as country, count(*) AS foaf
-    WHERE foaf > 1      
-    RETURN country.name, foaf
-$$) as (name agtype, foaf agtype);
+SELECT * FROM cypher('cypher_with',$$
+    MATCH (person)-[r]->(otherPerson)
+    WITH *, type(r) AS connectionType
+    RETURN person.name, connectionType, otherPerson.name
+$$) AS (start_node agtype, connection agtype, end_node agtype);
 
 SELECT * FROM cypher('cypher_with', $$
     WITH true AS b
@@ -72,28 +71,21 @@ $$) AS (b bool);
 -- WITH/WHERE
 
 SELECT * FROM cypher('cypher_with', $$
-    MATCH (m:City)-[:has_city]->(b:Country{iso2:'BE'})
-    WITH b,m 
-    WHERE m.name='x'
-    RETURN m.name,b.iso2
-$$) AS ( "m.name" agtype, "b" agtype);
-
-SELECT * FROM cypher('cypher_with', $$
-    MATCH (b:Country {iso2 : 'AT'}) 
-    WITH b.name AS name, id(b) AS id
-    WHERE name = 'Austria'
-    RETURN name,id
-$$) AS (Country agtype, Country_id agtype);
+MATCH (george {name: 'George'})<-[]-(otherPerson)
+    WITH otherPerson, toUpper(otherPerson.name) AS upperCaseName
+    WHERE upperCaseName STARTS WITH 'C'
+    RETURN otherPerson.name
+$$) as (name agtype);
 
 SELECT * FROM cypher('cypher_with', $$
-    MATCH (b:Country {iso2 : 'AT'}) 
-    WITH b.name AS name, id(b) AS id
-    WHERE name = 'Austria' OR name = 'Kosovo'
-    RETURN name,id                             
-$$) AS (Country agtype, Country_id agtype);
+	MATCH (david {name: 'David'})-[]-(otherPerson)-[]->()
+	WITH otherPerson, count(*) AS foaf
+	WHERE foaf > 1
+	RETURN otherPerson.name
+$$) as (name agtype);
 
 SELECT * FROM cypher('cypher_with', $$
-    MATCH p = (m:City)-[:has_city*1..2]->(b:Country {iso2 : 'AT'}) 
+    MATCH p = (m)-[*1..2]->(b) 
     WITH p, length(p) AS path_length 
     WHERE path_length > 1 
     RETURN p
@@ -102,66 +94,67 @@ $$) AS (pattern agtype);
 -- MATCH/WHERE with WITH/WHERE
 
 SELECT * FROM cypher('cypher_with', $$
-    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
-    WHERE b.name = 'Austria'
-    WITH m.name AS city,b.name AS country
-    WHERE city = 'Vienna'
-    RETURN city,country
-$$) AS (City agtype, Country agtype);
+    MATCH (m)-[e]->(b)
+    WHERE label(e) = 'KNOWS'
+    WITH *
+    WHERE m.name = 'Andres'
+    RETURN m.name,label(e),b.name
+$$) AS (N1 agtype, edge agtype, N2 agtype);
 
 -- WITH/ORDER BY
 
 SELECT * FROM cypher('cypher_with', $$
-    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
-    WITH m AS city,b AS country
-    ORDER BY id(m) DESC LIMIT 10
-    RETURN id(city),city
-$$) AS (id agtype, city agtype);
+    MATCH (n)
+    WITH n
+    ORDER BY id(n)
+    RETURN n
+$$) as (name agtype);
 
-SELECT * FROM cypher('cypher_with', $$
-    MATCH (m:City) 
-    WITH m AS city
-    ORDER BY id(m) ASC LIMIT 10
-    RETURN id(city),city.name
-$$) AS (id agtype, names agtype);
+-- WITH/ORDER BY/DESC
 
--- WITH/ORDER BY/DESC/WHERE
+SELECT * FROM cypher('cypher_with', $$
+    MATCH (n)
+    WITH n
+    ORDER BY n.name DESC LIMIT 3
+    RETURN collect(n.name)
+$$) as (names agtype);
 
 SELECT * FROM cypher('cypher_with', $$
-    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
-    WITH m AS city,b AS country
-    ORDER BY id(m) DESC LIMIT 10
-    WHERE city.name = 'Zell' OR city.name = 'Umberg'
-    RETURN id(city),city.name,country.name
-$$) AS (id agtype, city agtype, country agtype);
+    MATCH (n {name: 'Andres'})-[]-(m)
+    WITH m
+    ORDER BY m.name DESC LIMIT 1
+    MATCH (m)-[]-(o)
+    RETURN o.name ORDER BY o.name
+$$) as (name agtype);
 
 -- multiple WITH clauses
 
 SELECT * FROM cypher('cypher_with', $$
-    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
-    WITH m AS city,b AS country     
-    WITH city LIMIT 10
-    RETURN city.name
-$$) AS (city agtype);
+    MATCH (n)-[e]->(m)
+    WITH n, e, m
+    WHERE label(e) = 'KNOWS'
+    WITH n.name as n1, label(e) as edge, m.name as n2
+    WHERE n1 = 'Andres'
+    RETURN n1,edge,n2
+$$) AS (N1 agtype, edge agtype, N2 agtype);
 
 SELECT * FROM cypher('cypher_with', $$
-    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
-    WITH m AS city,b AS country
-    ORDER BY id(m) DESC LIMIT 10
-    WITH city
-    WHERE city.name = 'Zell'
-    RETURN id(city),city.name
-$$) AS (id agtype, city agtype);
+    UNWIND [1, 2, 3, 4, 5, 6] AS x
+    WITH x
+    WHERE x > 2
+    WITH x
+    LIMIT 5
+    RETURN x
+$$) as (name agtype);
 
 SELECT * FROM cypher('cypher_with', $$
-    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
-    WITH m AS city,b AS country
-    WHERE country.name = 'Austria'
-    WITH city
-    ORDER BY id(city) DESC
-    WHERE city.name = 'Zell'
-    RETURN id(city),city.name
-$$) AS (id agtype, city agtype);
+    MATCH (m)-[]->(b)
+    WITH m,b
+    ORDER BY id(m) DESC LIMIT 5
+    WITH m as start_node, b as end_node
+    WHERE end_node.name = 'George'
+    RETURN id(start_node),start_node.name,id(end_node),end_node.name
+$$) AS (id1 agtype, name1 agtype, id2 agtype, name2 agtype);
 
 -- Expression item must be aliased.
 
@@ -171,27 +164,27 @@ SELECT * FROM cypher('cypher_with', $$
 $$) AS (i int);
 
 SELECT * FROM cypher('cypher_with', $$
-    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
+    MATCH (m)-[]->(b)
     WITH id(m)
     RETURN m
-$$) AS (id agtype, city agtype);
+$$) AS (id agtype);
 
 -- Reference undefined variable in WITH clause (should error out)
 
 SELECT count(*) FROM cypher('cypher_with', $$
-    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'}) 
+    MATCH (m)-[]->(b)
     WITH m  
     RETURN m,b
-$$) AS (City agtype, Country agtype);
+$$) AS (a agtype, b agtype);
 
 SELECT * FROM cypher('cypher_with', $$
-    MATCH (m:City)-[:has_city]->(b:Country {iso2 : 'AT'})
-    WITH m AS city,b AS country
-    WHERE country.name = 'Austria'
-    WITH city
-    WHERE city.name = 'Zell'
-    RETURN id(city),country.name
-$$) AS (id agtype, country agtype);
+    MATCH (m)-[]->(b)
+    WITH m AS start_node,b AS end_node
+    WHERE start_node.name = 'Andres'
+    WITH start_node
+    WHERE start_node.name = 'George'
+    RETURN id(start_node),end_node.name
+$$) AS (id agtype, node agtype);
 
 -- Clean up
 
@@ -219,15 +212,15 @@ $$) AS (n agtype, d agtype);
 -- Issue 396 (should error out)
 
 SELECT * FROM cypher('graph',$$
-     CREATE (v),(u),(w),
-            (v)-[:hasFriend]->(u),
-            (u)-[:hasFriend]->(w)
+    CREATE (v),(u),(w),
+        (v)-[:hasFriend]->(u),
+        (u)-[:hasFriend]->(w)
 $$) as (a agtype);
 
 SELECT * FROM cypher('graph',$$
-      MATCH p=(v)-[*1..2]->(u) 
-      WITH p,length(p) AS path_length 
-      RETURN v,path_length
+    MATCH p=(v)-[*1..2]->(u) 
+    WITH p,length(p) AS path_length 
+    RETURN v,path_length
 $$) as (a agtype,b agtype);
 
 -- Clean up


[age] 19/19: Fix property constraints against resolved variables in concurrent MATCH clauses(#701) (#747)

Posted by jg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 1a7032859f1ce14d8127bdf8953eac44379adcf0
Author: John Gemignani <jr...@gmail.com>
AuthorDate: Thu Mar 16 19:42:17 2023 -0700

    Fix property constraints against resolved variables in concurrent MATCH clauses(#701) (#747)
    
    Fixed an issue where an already resolved variable, when used for
    a property constraint in a second MATCH clause, errored out.
    See #701 and #724  for more details. This is part of a series of
    PRs done to refactor MATCH clause behavior.
    
    This also fixed a regression test that was erroring out, but was
    overlooked.
    
    Adjusted and added additional regression tests.
    
    Co-authored-by: Dehowe Feng <de...@gmail.com>
---
 regress/expected/cypher_match.out  | 225 ++++++++++++++++++++++++++++++++++++-
 regress/expected/cypher_merge.out  |  40 +++----
 regress/sql/cypher_match.sql       |  73 ++++++++++++
 src/backend/parser/cypher_clause.c | 167 +++++++++++++++++++++++----
 4 files changed, 461 insertions(+), 44 deletions(-)

diff --git a/regress/expected/cypher_match.out b/regress/expected/cypher_match.out
index 4888556d..f3817a50 100644
--- a/regress/expected/cypher_match.out
+++ b/regress/expected/cypher_match.out
@@ -1450,11 +1450,233 @@ $$) as (n agtype);
  {"id": 281474976710662, "label": "", "properties": {"i": 1, "j": 2, "k": 3}}::vertex
 (1 row)
 
+--
+-- Regression tests to check previous clause variable refs
+--
+-- set up initial state and show what we're working with
+SELECT * FROM cypher('cypher_match', $$
+    CREATE (a {age: 4}) RETURN a $$) as (a agtype);
+                                   a                                    
+------------------------------------------------------------------------
+ {"id": 281474976710665, "label": "", "properties": {"age": 4}}::vertex
+(1 row)
+
+SELECT * FROM cypher('cypher_match', $$
+	CREATE (b {age: 6}) RETURN b $$) as (b agtype);
+                                   b                                    
+------------------------------------------------------------------------
+ {"id": 281474976710666, "label": "", "properties": {"age": 6}}::vertex
+(1 row)
+
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a) RETURN a $$) as (a agtype);
+                                          a                                           
+--------------------------------------------------------------------------------------
+ {"id": 281474976710659, "label": "", "properties": {"name": "orphan"}}::vertex
+ {"id": 281474976710660, "label": "", "properties": {"name": "F"}}::vertex
+ {"id": 281474976710661, "label": "", "properties": {"name": "T"}}::vertex
+ {"id": 281474976710662, "label": "", "properties": {"i": 1, "j": 2, "k": 3}}::vertex
+ {"id": 281474976710663, "label": "", "properties": {"i": 1, "j": 3}}::vertex
+ {"id": 281474976710664, "label": "", "properties": {"i": 2, "k": 3}}::vertex
+ {"id": 281474976710665, "label": "", "properties": {"age": 4}}::vertex
+ {"id": 281474976710666, "label": "", "properties": {"age": 6}}::vertex
+(8 rows)
+
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a) WHERE exists(a.name) RETURN a $$) as (a agtype);
+                                       a                                        
+--------------------------------------------------------------------------------
+ {"id": 281474976710659, "label": "", "properties": {"name": "orphan"}}::vertex
+ {"id": 281474976710660, "label": "", "properties": {"name": "F"}}::vertex
+ {"id": 281474976710661, "label": "", "properties": {"name": "T"}}::vertex
+(3 rows)
+
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a) WHERE exists(a.name) SET a.age = 4 RETURN a $$) as (a agtype);
+                                            a                                             
+------------------------------------------------------------------------------------------
+ {"id": 281474976710659, "label": "", "properties": {"age": 4, "name": "orphan"}}::vertex
+ {"id": 281474976710660, "label": "", "properties": {"age": 4, "name": "F"}}::vertex
+ {"id": 281474976710661, "label": "", "properties": {"age": 4, "name": "T"}}::vertex
+(3 rows)
+
+SELECT * FROM cypher('cypher_match', $$
+	MATCH (a),(b) WHERE a.age = 4 AND a.name = "T" AND b.age = 6
+	RETURN a,b $$) as (a agtype, b agtype);
+                                          a                                          |                                   b                                    
+-------------------------------------------------------------------------------------+------------------------------------------------------------------------
+ {"id": 281474976710661, "label": "", "properties": {"age": 4, "name": "T"}}::vertex | {"id": 281474976710666, "label": "", "properties": {"age": 6}}::vertex
+(1 row)
+
+SELECT * FROM cypher('cypher_match', $$
+	MATCH (a),(b) WHERE a.age = 4 AND a.name = "T" AND b.age = 6 CREATE 
+	(a)-[:knows {relationship: "friends", years: 3}]->(b) $$) as (r agtype);
+ r 
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_match', $$
+	MATCH (a),(b) WHERE a.age = 4 AND a.name = "orphan" AND b.age = 6 CREATE 
+	(a)-[:knows {relationship: "enemies", years: 4}]->(b) $$) as (r agtype);
+ r 
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_match', $$
+	MATCH (a)-[r]-(b) RETURN r $$) as (r agtype);
+                                                                                r                                                                                
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {"id": 4785074604081153, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710661, "properties": {"years": 3, "relationship": "friends"}}::edge
+ {"id": 4785074604081153, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710661, "properties": {"years": 3, "relationship": "friends"}}::edge
+ {"id": 4785074604081154, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710659, "properties": {"years": 4, "relationship": "enemies"}}::edge
+ {"id": 4785074604081154, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710659, "properties": {"years": 4, "relationship": "enemies"}}::edge
+ {"id": 1407374883553283, "label": "e1", "end_id": 281474976710661, "start_id": 281474976710660, "properties": {}}::edge
+ {"id": 1407374883553283, "label": "e1", "end_id": 281474976710661, "start_id": 281474976710660, "properties": {}}::edge
+(6 rows)
+
+-- check reuse of 'a'
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a {age:4}) RETURN a $$) as (a agtype);
+                                            a                                             
+------------------------------------------------------------------------------------------
+ {"id": 281474976710665, "label": "", "properties": {"age": 4}}::vertex
+ {"id": 281474976710659, "label": "", "properties": {"age": 4, "name": "orphan"}}::vertex
+ {"id": 281474976710660, "label": "", "properties": {"age": 4, "name": "F"}}::vertex
+ {"id": 281474976710661, "label": "", "properties": {"age": 4, "name": "T"}}::vertex
+(4 rows)
+
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a) MATCH (a {age:4}) RETURN a $$) as (a agtype);
+                                            a                                             
+------------------------------------------------------------------------------------------
+ {"id": 281474976710665, "label": "", "properties": {"age": 4}}::vertex
+ {"id": 281474976710659, "label": "", "properties": {"age": 4, "name": "orphan"}}::vertex
+ {"id": 281474976710660, "label": "", "properties": {"age": 4, "name": "F"}}::vertex
+ {"id": 281474976710661, "label": "", "properties": {"age": 4, "name": "T"}}::vertex
+(4 rows)
+
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a {age:4, name: "orphan"}) RETURN a $$) as (a agtype);
+                                            a                                             
+------------------------------------------------------------------------------------------
+ {"id": 281474976710659, "label": "", "properties": {"age": 4, "name": "orphan"}}::vertex
+(1 row)
+
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a) MATCH (a {age:4}) MATCH (a {name: "orphan"}) RETURN a $$) as (a agtype);
+                                            a                                             
+------------------------------------------------------------------------------------------
+ {"id": 281474976710659, "label": "", "properties": {"age": 4, "name": "orphan"}}::vertex
+(1 row)
+
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a {age:4}) MATCH (a {name: "orphan"}) RETURN a $$) as (a agtype);
+                                            a                                             
+------------------------------------------------------------------------------------------
+ {"id": 281474976710659, "label": "", "properties": {"age": 4, "name": "orphan"}}::vertex
+(1 row)
+
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a) MATCH (a {age:4}) MATCH (a {name: "orphan"}) SET a.age = 3 RETURN a $$) as (a agtype);
+                                            a                                             
+------------------------------------------------------------------------------------------
+ {"id": 281474976710659, "label": "", "properties": {"age": 3, "name": "orphan"}}::vertex
+(1 row)
+
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a) MATCH (a {age:3}) MATCH (a {name: "orphan"}) RETURN a $$) as (a agtype);
+                                            a                                             
+------------------------------------------------------------------------------------------
+ {"id": 281474976710659, "label": "", "properties": {"age": 3, "name": "orphan"}}::vertex
+(1 row)
+
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a {name: "orphan"}) MATCH (a {age:3}) RETURN a $$) as (a agtype);
+                                            a                                             
+------------------------------------------------------------------------------------------
+ {"id": 281474976710659, "label": "", "properties": {"age": 3, "name": "orphan"}}::vertex
+(1 row)
+
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a) WHERE exists(a.age) AND exists(a.name) RETURN a $$) as (a agtype);
+                                            a                                             
+------------------------------------------------------------------------------------------
+ {"id": 281474976710660, "label": "", "properties": {"age": 4, "name": "F"}}::vertex
+ {"id": 281474976710661, "label": "", "properties": {"age": 4, "name": "T"}}::vertex
+ {"id": 281474976710659, "label": "", "properties": {"age": 3, "name": "orphan"}}::vertex
+(3 rows)
+
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a) WHERE exists(a.age) AND NOT exists(a.name) RETURN a $$) as (a agtype);
+                                   a                                    
+------------------------------------------------------------------------
+ {"id": 281474976710665, "label": "", "properties": {"age": 4}}::vertex
+ {"id": 281474976710666, "label": "", "properties": {"age": 6}}::vertex
+(2 rows)
+
+-- check reuse of 'r'
+SELECT * FROM cypher('cypher_match', $$
+	MATCH ()-[r]-() RETURN r $$) as (r agtype);
+                                                                                r                                                                                
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {"id": 4785074604081153, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710661, "properties": {"years": 3, "relationship": "friends"}}::edge
+ {"id": 4785074604081153, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710661, "properties": {"years": 3, "relationship": "friends"}}::edge
+ {"id": 4785074604081154, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710659, "properties": {"years": 4, "relationship": "enemies"}}::edge
+ {"id": 4785074604081154, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710659, "properties": {"years": 4, "relationship": "enemies"}}::edge
+ {"id": 1407374883553283, "label": "e1", "end_id": 281474976710661, "start_id": 281474976710660, "properties": {}}::edge
+ {"id": 1407374883553283, "label": "e1", "end_id": 281474976710661, "start_id": 281474976710660, "properties": {}}::edge
+(6 rows)
+
+SELECT * FROM cypher('cypher_match', $$
+	MATCH ()-[r]-() MATCH ()-[r {relationship: "friends"}]-() RETURN r $$) as (r agtype);
+                                                                                r                                                                                
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {"id": 4785074604081153, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710661, "properties": {"years": 3, "relationship": "friends"}}::edge
+ {"id": 4785074604081153, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710661, "properties": {"years": 3, "relationship": "friends"}}::edge
+(2 rows)
+
+SELECT * FROM cypher('cypher_match', $$
+	MATCH ()-[r {years:3, relationship: "friends"}]-() RETURN r $$) as (r agtype);
+                                                                                r                                                                                
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {"id": 4785074604081153, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710661, "properties": {"years": 3, "relationship": "friends"}}::edge
+ {"id": 4785074604081153, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710661, "properties": {"years": 3, "relationship": "friends"}}::edge
+(2 rows)
+
+SELECT * FROM cypher('cypher_match', $$
+	MATCH ()-[r {years:3}]-() MATCH ()-[r {relationship: "friends"}]-() RETURN r $$) as (r agtype);
+                                                                                r                                                                                
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {"id": 4785074604081153, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710661, "properties": {"years": 3, "relationship": "friends"}}::edge
+ {"id": 4785074604081153, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710661, "properties": {"years": 3, "relationship": "friends"}}::edge
+(2 rows)
+
+--mismatch year #, should return nothing
+SELECT * FROM cypher('cypher_match', $$
+	MATCH ()-[r {years:2}]-() MATCH ()-[r {relationship: "friends"}]-() RETURN r $$) as (r agtype);
+ r 
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_match', $$
+	MATCH ()-[r {relationship:"enemies"}]-() MATCH ()-[r {years:4}]-() RETURN r $$) as (r agtype);
+                                                                                r                                                                                
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {"id": 4785074604081154, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710659, "properties": {"years": 4, "relationship": "enemies"}}::edge
+ {"id": 4785074604081154, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710659, "properties": {"years": 4, "relationship": "enemies"}}::edge
+(2 rows)
+
+SELECT * FROM cypher('cypher_match', $$
+	MATCH ()-[r {relationship:"enemies"}]-() MATCH ()-[r {relationship:"friends"}]-() RETURN r $$) as (r agtype);
+ r 
+---
+(0 rows)
+
 --
 -- Clean up
 --
 SELECT drop_graph('cypher_match', true);
-NOTICE:  drop cascades to 16 other objects
+NOTICE:  drop cascades to 17 other objects
 DETAIL:  drop cascades to table cypher_match._ag_label_vertex
 drop cascades to table cypher_match._ag_label_edge
 drop cascades to table cypher_match.v
@@ -1471,6 +1693,7 @@ drop cascades to table cypher_match.dup_edge
 drop cascades to table cypher_match.other_v
 drop cascades to table cypher_match.opt_match_v
 drop cascades to table cypher_match.opt_match_e
+drop cascades to table cypher_match.knows
 NOTICE:  graph "cypher_match" has been dropped
  drop_graph 
 ------------
diff --git a/regress/expected/cypher_merge.out b/regress/expected/cypher_merge.out
index 389c34d1..988dff3c 100644
--- a/regress/expected/cypher_merge.out
+++ b/regress/expected/cypher_merge.out
@@ -513,15 +513,17 @@ SELECT * FROM cypher('cypher_merge', $$MATCH (n) DETACH DELETE n $$) AS (a agtyp
  */
 --test query
 SELECT * FROM cypher('cypher_merge', $$CREATE (n {i : 1}) SET n.i = 2 MERGE ({i: 2}) $$) AS (a agtype);
-ERROR:  missing FROM-clause entry for table "_age_default_alias_0"
-LINE 5: SELECT * FROM cypher('cypher_merge', $$CREATE (n {i : 1}) SE...
-                                               ^
---validate created correctly
-SELECT * FROM cypher('cypher_merge', $$MATCH (a) RETURN a$$) AS (a agtype);
  a 
 ---
 (0 rows)
 
+--validate created correctly
+SELECT * FROM cypher('cypher_merge', $$MATCH (a) RETURN a$$) AS (a agtype);
+                                  a                                   
+----------------------------------------------------------------------
+ {"id": 281474976710690, "label": "", "properties": {"i": 2}}::vertex
+(1 row)
+
 --clean up
 SELECT * FROM cypher('cypher_merge', $$MATCH (n) DETACH DELETE n $$) AS (a agtype);
  a 
@@ -541,7 +543,7 @@ SELECT * FROM cypher('cypher_merge', $$CREATE (n {i : 1}) SET n.i = 2 WITH n as
 SELECT * FROM cypher('cypher_merge', $$MATCH (a) RETURN a$$) AS (a agtype);
                                   a                                   
 ----------------------------------------------------------------------
- {"id": 281474976710690, "label": "", "properties": {"i": 2}}::vertex
+ {"id": 281474976710691, "label": "", "properties": {"i": 2}}::vertex
 (1 row)
 
 --clean up
@@ -569,7 +571,7 @@ SELECT * FROM cypher('cypher_merge', $$MATCH (n {i : 1}) SET n.i = 2 WITH n as a
 SELECT * FROM cypher('cypher_merge', $$MATCH (a) RETURN a$$) AS (a agtype);
                                   a                                   
 ----------------------------------------------------------------------
- {"id": 281474976710691, "label": "", "properties": {"i": 2}}::vertex
+ {"id": 281474976710692, "label": "", "properties": {"i": 2}}::vertex
 (1 row)
 
 --clean up
@@ -594,7 +596,7 @@ ERROR:  vertex assigned to variable n was deleted
 SELECT * FROM cypher('cypher_merge', $$MATCH (a) RETURN a$$) AS (a agtype);
                                   a                                   
 ----------------------------------------------------------------------
- {"id": 281474976710692, "label": "", "properties": {"i": 1}}::vertex
+ {"id": 281474976710693, "label": "", "properties": {"i": 1}}::vertex
 (1 row)
 
 --clean up
@@ -672,7 +674,7 @@ SELECT * FROM cypher('cypher_merge', $$MERGE ()-[:e]-()$$) AS (a agtype);
 SELECT * FROM cypher('cypher_merge', $$MATCH p=()-[]->() RETURN p$$) AS (a agtype);
                                                                                                                                a                                                                                                                               
 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- [{"id": 281474976710693, "label": "", "properties": {}}::vertex, {"id": 844424930131982, "label": "e", "end_id": 281474976710694, "start_id": 281474976710693, "properties": {}}::edge, {"id": 281474976710694, "label": "", "properties": {}}::vertex]::path
+ [{"id": 281474976710694, "label": "", "properties": {}}::vertex, {"id": 844424930131982, "label": "e", "end_id": 281474976710695, "start_id": 281474976710694, "properties": {}}::edge, {"id": 281474976710695, "label": "", "properties": {}}::vertex]::path
 (1 row)
 
 --clean up
@@ -687,14 +689,14 @@ SELECT * FROM cypher('cypher_merge', $$MATCH (n) DETACH DELETE n $$) AS (a agtyp
 SELECT * FROM cypher('cypher_merge', $$MERGE (a) RETURN a$$) AS (a agtype);
                                a                                
 ----------------------------------------------------------------
- {"id": 281474976710695, "label": "", "properties": {}}::vertex
+ {"id": 281474976710696, "label": "", "properties": {}}::vertex
 (1 row)
 
 --validate
 SELECT * FROM cypher('cypher_merge', $$MATCH (a) RETURN a$$) AS (a agtype);
                                a                                
 ----------------------------------------------------------------
- {"id": 281474976710695, "label": "", "properties": {}}::vertex
+ {"id": 281474976710696, "label": "", "properties": {}}::vertex
 (1 row)
 
 --clean up
@@ -709,14 +711,14 @@ SELECT * FROM cypher('cypher_merge', $$MATCH (n) DETACH DELETE n $$) AS (a agtyp
 SELECT * FROM cypher('cypher_merge', $$MERGE p=()-[:e]-() RETURN p$$) AS (a agtype);
                                                                                                                                a                                                                                                                               
 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- [{"id": 281474976710696, "label": "", "properties": {}}::vertex, {"id": 844424930131983, "label": "e", "end_id": 281474976710697, "start_id": 281474976710696, "properties": {}}::edge, {"id": 281474976710697, "label": "", "properties": {}}::vertex]::path
+ [{"id": 281474976710697, "label": "", "properties": {}}::vertex, {"id": 844424930131983, "label": "e", "end_id": 281474976710698, "start_id": 281474976710697, "properties": {}}::edge, {"id": 281474976710698, "label": "", "properties": {}}::vertex]::path
 (1 row)
 
 --validate
 SELECT * FROM cypher('cypher_merge', $$MATCH p=()-[]->() RETURN p$$) AS (a agtype);
                                                                                                                                a                                                                                                                               
 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- [{"id": 281474976710696, "label": "", "properties": {}}::vertex, {"id": 844424930131983, "label": "e", "end_id": 281474976710697, "start_id": 281474976710696, "properties": {}}::edge, {"id": 281474976710697, "label": "", "properties": {}}::vertex]::path
+ [{"id": 281474976710697, "label": "", "properties": {}}::vertex, {"id": 844424930131983, "label": "e", "end_id": 281474976710698, "start_id": 281474976710697, "properties": {}}::edge, {"id": 281474976710698, "label": "", "properties": {}}::vertex]::path
 (1 row)
 
 --clean up
@@ -731,14 +733,14 @@ SELECT * FROM cypher('cypher_merge', $$MATCH (n) DETACH DELETE n $$) AS (a agtyp
 SELECT * FROM cypher('cypher_merge', $$MERGE (a)-[:e]-(b) RETURN a$$) AS (a agtype);
                                a                                
 ----------------------------------------------------------------
- {"id": 281474976710698, "label": "", "properties": {}}::vertex
+ {"id": 281474976710699, "label": "", "properties": {}}::vertex
 (1 row)
 
 --validate
 SELECT * FROM cypher('cypher_merge', $$MATCH p=()-[]->() RETURN p$$) AS (a agtype);
                                                                                                                                a                                                                                                                               
 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- [{"id": 281474976710698, "label": "", "properties": {}}::vertex, {"id": 844424930131984, "label": "e", "end_id": 281474976710699, "start_id": 281474976710698, "properties": {}}::edge, {"id": 281474976710699, "label": "", "properties": {}}::vertex]::path
+ [{"id": 281474976710699, "label": "", "properties": {}}::vertex, {"id": 844424930131984, "label": "e", "end_id": 281474976710700, "start_id": 281474976710699, "properties": {}}::edge, {"id": 281474976710700, "label": "", "properties": {}}::vertex]::path
 (1 row)
 
 --clean up
@@ -753,14 +755,14 @@ SELECT * FROM cypher('cypher_merge', $$MATCH (n) DETACH DELETE n $$) AS (a agtyp
 SELECT  * FROM cypher('cypher_merge', $$CREATE p=()-[:e]->() RETURN p$$) AS (a agtype);
                                                                                                                                a                                                                                                                               
 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- [{"id": 281474976710700, "label": "", "properties": {}}::vertex, {"id": 844424930131985, "label": "e", "end_id": 281474976710701, "start_id": 281474976710700, "properties": {}}::edge, {"id": 281474976710701, "label": "", "properties": {}}::vertex]::path
+ [{"id": 281474976710701, "label": "", "properties": {}}::vertex, {"id": 844424930131985, "label": "e", "end_id": 281474976710702, "start_id": 281474976710701, "properties": {}}::edge, {"id": 281474976710702, "label": "", "properties": {}}::vertex]::path
 (1 row)
 
 SELECT * FROM cypher('cypher_merge', $$MERGE p=()-[:e]-() RETURN p$$) AS (a agtype);
                                                                                                                                a                                                                                                                               
 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- [{"id": 281474976710700, "label": "", "properties": {}}::vertex, {"id": 844424930131985, "label": "e", "end_id": 281474976710701, "start_id": 281474976710700, "properties": {}}::edge, {"id": 281474976710701, "label": "", "properties": {}}::vertex]::path
- [{"id": 281474976710701, "label": "", "properties": {}}::vertex, {"id": 844424930131985, "label": "e", "end_id": 281474976710701, "start_id": 281474976710700, "properties": {}}::edge, {"id": 281474976710700, "label": "", "properties": {}}::vertex]::path
+ [{"id": 281474976710701, "label": "", "properties": {}}::vertex, {"id": 844424930131985, "label": "e", "end_id": 281474976710702, "start_id": 281474976710701, "properties": {}}::edge, {"id": 281474976710702, "label": "", "properties": {}}::vertex]::path
+ [{"id": 281474976710702, "label": "", "properties": {}}::vertex, {"id": 844424930131985, "label": "e", "end_id": 281474976710702, "start_id": 281474976710701, "properties": {}}::edge, {"id": 281474976710701, "label": "", "properties": {}}::vertex]::path
 (2 rows)
 
 --clean up
@@ -805,7 +807,7 @@ ERROR:  Existing variable m cannot be NULL in MERGE clause
 SELECT * FROM cypher('cypher_merge', $$MATCH (n) RETURN n$$) AS (a agtype);
                                a                                
 ----------------------------------------------------------------
- {"id": 281474976710702, "label": "", "properties": {}}::vertex
+ {"id": 281474976710703, "label": "", "properties": {}}::vertex
 (1 row)
 
 --
diff --git a/regress/sql/cypher_match.sql b/regress/sql/cypher_match.sql
index 344453e0..f08f8dfb 100644
--- a/regress/sql/cypher_match.sql
+++ b/regress/sql/cypher_match.sql
@@ -734,6 +734,79 @@ SELECT * FROM cypher('cypher_match', $$
     RETURN n
 $$) as (n agtype);
 
+--
+-- Regression tests to check previous clause variable refs
+--
+-- set up initial state and show what we're working with
+SELECT * FROM cypher('cypher_match', $$
+    CREATE (a {age: 4}) RETURN a $$) as (a agtype);
+SELECT * FROM cypher('cypher_match', $$
+	CREATE (b {age: 6}) RETURN b $$) as (b agtype);
+
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a) RETURN a $$) as (a agtype);
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a) WHERE exists(a.name) RETURN a $$) as (a agtype);
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a) WHERE exists(a.name) SET a.age = 4 RETURN a $$) as (a agtype);
+
+
+SELECT * FROM cypher('cypher_match', $$
+	MATCH (a),(b) WHERE a.age = 4 AND a.name = "T" AND b.age = 6
+	RETURN a,b $$) as (a agtype, b agtype);
+SELECT * FROM cypher('cypher_match', $$
+	MATCH (a),(b) WHERE a.age = 4 AND a.name = "T" AND b.age = 6 CREATE 
+	(a)-[:knows {relationship: "friends", years: 3}]->(b) $$) as (r agtype);
+SELECT * FROM cypher('cypher_match', $$
+	MATCH (a),(b) WHERE a.age = 4 AND a.name = "orphan" AND b.age = 6 CREATE 
+	(a)-[:knows {relationship: "enemies", years: 4}]->(b) $$) as (r agtype);
+SELECT * FROM cypher('cypher_match', $$
+	MATCH (a)-[r]-(b) RETURN r $$) as (r agtype);
+
+-- check reuse of 'a'
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a {age:4}) RETURN a $$) as (a agtype);
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a) MATCH (a {age:4}) RETURN a $$) as (a agtype);
+
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a {age:4, name: "orphan"}) RETURN a $$) as (a agtype);
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a) MATCH (a {age:4}) MATCH (a {name: "orphan"}) RETURN a $$) as (a agtype);
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a {age:4}) MATCH (a {name: "orphan"}) RETURN a $$) as (a agtype);
+
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a) MATCH (a {age:4}) MATCH (a {name: "orphan"}) SET a.age = 3 RETURN a $$) as (a agtype);
+
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a) MATCH (a {age:3}) MATCH (a {name: "orphan"}) RETURN a $$) as (a agtype);
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a {name: "orphan"}) MATCH (a {age:3}) RETURN a $$) as (a agtype);
+
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a) WHERE exists(a.age) AND exists(a.name) RETURN a $$) as (a agtype);
+
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (a) WHERE exists(a.age) AND NOT exists(a.name) RETURN a $$) as (a agtype);
+
+-- check reuse of 'r'
+SELECT * FROM cypher('cypher_match', $$
+	MATCH ()-[r]-() RETURN r $$) as (r agtype);
+SELECT * FROM cypher('cypher_match', $$
+	MATCH ()-[r]-() MATCH ()-[r {relationship: "friends"}]-() RETURN r $$) as (r agtype);
+SELECT * FROM cypher('cypher_match', $$
+	MATCH ()-[r {years:3, relationship: "friends"}]-() RETURN r $$) as (r agtype);
+SELECT * FROM cypher('cypher_match', $$
+	MATCH ()-[r {years:3}]-() MATCH ()-[r {relationship: "friends"}]-() RETURN r $$) as (r agtype);
+--mismatch year #, should return nothing
+SELECT * FROM cypher('cypher_match', $$
+	MATCH ()-[r {years:2}]-() MATCH ()-[r {relationship: "friends"}]-() RETURN r $$) as (r agtype);
+SELECT * FROM cypher('cypher_match', $$
+	MATCH ()-[r {relationship:"enemies"}]-() MATCH ()-[r {years:4}]-() RETURN r $$) as (r agtype);
+SELECT * FROM cypher('cypher_match', $$
+	MATCH ()-[r {relationship:"enemies"}]-() MATCH ()-[r {relationship:"friends"}]-() RETURN r $$) as (r agtype);
+
 --
 -- Clean up
 --
diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c
index 526a23fb..01b33605 100644
--- a/src/backend/parser/cypher_clause.c
+++ b/src/backend/parser/cypher_clause.c
@@ -163,7 +163,8 @@ static A_Expr *filter_vertices_on_label_id(cypher_parsestate *cpstate,
                                            Node *id_field, char *label);
 static Node *create_property_constraints(cypher_parsestate *cpstate,
                                          transform_entity *entity,
-                                         Node *property_constraints);
+                                         Node *property_constraints,
+                                         Node *prop_expr);
 static TargetEntry *findTarget(List *targetList, char *resname);
 static transform_entity *transform_VLE_edge_entity(cypher_parsestate *cpstate,
                                                    cypher_relationship *rel,
@@ -3497,33 +3498,43 @@ static A_Expr *filter_vertices_on_label_id(cypher_parsestate *cpstate,
  */
 static Node *create_property_constraints(cypher_parsestate *cpstate,
                                          transform_entity *entity,
-                                         Node *property_constraints)
+                                         Node *property_constraints,
+                                         Node *prop_expr)
 {
     ParseState *pstate = (ParseState *)cpstate;
     char *entity_name;
-    ColumnRef *cr;
-    Node *prop_expr, *const_expr;
+    Node *const_expr;
     RangeTblEntry *rte;
     Node *last_srf = pstate->p_last_srf;
 
-    cr = makeNode(ColumnRef);
-
-    entity_name = get_entity_name(entity);
+    /*
+     * If the prop_expr node wasn't passed in, create it. Otherwise, skip
+     * the creation step.
+     */
+    if (prop_expr == NULL)
+    {
+        ColumnRef *cr = NULL;
 
-    cr->fields = list_make2(makeString(entity_name), makeString("properties"));
+        cr = makeNode(ColumnRef);
+        entity_name = get_entity_name(entity);
+        cr->fields = list_make2(makeString(entity_name),
+                                makeString("properties"));
 
-    // use Postgres to get the properties' transform node
-    if ((rte = find_rte(cpstate, entity_name)))
-    {
-        prop_expr = scanRTEForColumn(pstate, rte, AG_VERTEX_COLNAME_PROPERTIES,
-                                     -1, 0, NULL);
-    }
-    else
-    {
-        prop_expr = transformExpr(pstate, (Node *)cr, EXPR_KIND_WHERE);
+        /* use Postgres to get the properties' transform node */
+        rte = find_rte(cpstate, entity_name);
+        if (rte != NULL)
+        {
+            prop_expr = scanRTEForColumn(pstate, rte,
+                                         AG_VERTEX_COLNAME_PROPERTIES, -1, 0,
+                                         NULL);
+        }
+        else
+        {
+            prop_expr = transformExpr(pstate, (Node *)cr, EXPR_KIND_WHERE);
+        }
     }
 
-    // use cypher to get the constraints' transform node
+    /* use cypher to get the constraints' transform node */
     const_expr = transform_cypher_expr(cpstate, property_constraints,
                                        EXPR_KIND_WHERE);
 
@@ -3808,13 +3819,68 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query,
             entity = make_transform_entity(cpstate, ENT_VERTEX, (Node *)node,
                                            expr);
 
-            /* transform properties if they exist */
+            /* transform the properties if they exist */
             if (node->props)
             {
                 Node *n = NULL;
+                Node *prop_var = NULL;
+                Node *prop_expr = NULL;
+
+                /*
+                 * We need to build a transformed properties(prop_var)
+                 * expression IF the properties variable already exists from a
+                 * previous clause. Please note that the "found" prop_var was
+                 * previously transformed.
+                 */
+
+                /* get the prop_var if it was previously resolved */
+                if (node->name != NULL)
+                {
+                    prop_var = colNameToVar(pstate, node->name, false,
+                                            node->location);
+                }
+
+                /*
+                 * If prop_var exists and is an alias, just pass it through by
+                 * assigning the prop_expr the prop_var.
+                 */
+                if (prop_var != NULL &&
+                    pg_strncasecmp(node->name, AGE_DEFAULT_ALIAS_PREFIX,
+                                   strlen(AGE_DEFAULT_ALIAS_PREFIX)) == 0)
+                {
+                    prop_expr = prop_var;
+                }
+                /*
+                 * Else, if it exists and is not an alias, create the prop_expr
+                 * as a transformed properties(prop_var) function node.
+                 */
+                else if (prop_var != NULL)
+                {
+                    /*
+                     * Remember that prop_var is already transformed. We need
+                     * to built the transform manually.
+                     */
+                    FuncCall *fc = NULL;
+                    List *targs = NIL;
+                    List *fname = NIL;
+
+                    targs = lappend(targs, prop_var);
+                    fname = list_make2(makeString("ag_catalog"),
+                                       makeString("age_properties"));
+                    fc = makeFuncCall(fname, targs, -1);
+
+                    /*
+                     * Hand off to ParseFuncOrColumn to create the function
+                     * expression for properties(prop_var)
+                     */
+                    prop_expr = ParseFuncOrColumn(pstate, fname, targs,
+                                                  pstate->p_last_srf, fc, false,
+                                                  -1);
+                }
 
                 ((cypher_map*)node->props)->keep_null = true;
-                n = create_property_constraints(cpstate, entity, node->props);
+                n = create_property_constraints(cpstate, entity, node->props,
+                                                prop_expr);
 
                 cpstate->property_constraint_quals =
                     lappend(cpstate->property_constraint_quals, n);
@@ -3873,13 +3939,66 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query,
 
                 if (rel->props)
                 {
-                    Node *n;
+                    Node *r = NULL;
+                    Node *prop_var = NULL;
+                    Node *prop_expr = NULL;
+
+                    /*
+                     * We need to build a transformed properties(prop_var)
+                     * expression IF the properties variable already exists from
+                     * a previous clause. Please note that the "found" prop_var
+                     * was previously transformed.
+                     */
+
+                    /* get the prop_var if it was previously resolved */
+                    if (rel->name != NULL)
+                    {
+                        prop_var = colNameToVar(pstate, rel->name, false,
+                                                rel->location);
+                    }
+
+                    /*
+                     * If prop_var exists and is an alias, just pass it through by
+                     * assigning the prop_expr the prop_var.
+                     */
+                    if (prop_var != NULL &&
+                        pg_strncasecmp(rel->name, AGE_DEFAULT_ALIAS_PREFIX,
+                                       strlen(AGE_DEFAULT_ALIAS_PREFIX)) == 0)
+                    {
+                        prop_expr = prop_var;
+                    }
+                    /*
+                     * Else, if it exists and is not an alias, create the prop_expr
+                     * as a transformed properties(prop_var) function node.
+                     */
+                    else if (prop_var != NULL)
+                    {
+                        /*
+                         * Remember that prop_var is already transformed. We need
+                         * to built the transform manually.
+                         */
+                        FuncCall *fc = NULL;
+                        List *targs = NIL;
+                        List *fname = NIL;
+
+                        targs = lappend(targs, prop_var);
+                        fname = list_make2(makeString("ag_catalog"),
+                                           makeString("age_properties"));
+                        fc = makeFuncCall(fname, targs, -1);
+
+                        /*
+                         * Hand off to ParseFuncOrColumn to create the function
+                         * expression for properties(prop_var)
+                         */
+                        prop_expr = ParseFuncOrColumn(pstate, fname, targs,
+                                                      pstate->p_last_srf, fc,
+                                                      false, -1);
+                    }
 
                     ((cypher_map*)rel->props)->keep_null = true;
-                    n = create_property_constraints(cpstate, entity,
-                                                          rel->props);
+                    r = create_property_constraints(cpstate, entity, rel->props, prop_expr);
                     cpstate->property_constraint_quals =
-                        lappend(cpstate->property_constraint_quals, n);
+                        lappend(cpstate->property_constraint_quals, r);
                 }
 
                 entities = lappend(entities, entity);


[age] 17/19: Prevent MATCH from following OPTIONAL MATCH (#740)

Posted by jg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 082209305bb7553c025f6cab3a586389dbab8c70
Author: Dehowe Feng <80...@users.noreply.github.com>
AuthorDate: Sat Mar 11 09:50:57 2023 +0900

    Prevent MATCH from following OPTIONAL MATCH (#740)
    
    Added logic to prevent match from following optional match. This
    syntax is considered invalid, add logic to throw an error when
    this happens.
    
    Also added regression tests to test this logic.
---
 regress/expected/cypher_match.out  | 19 +++++++++++++++++++
 regress/sql/cypher_match.sql       | 14 ++++++++++++++
 src/backend/parser/cypher_clause.c | 17 +++++++++++++++++
 3 files changed, 50 insertions(+)

diff --git a/regress/expected/cypher_match.out b/regress/expected/cypher_match.out
index 8f325cbe..4888556d 100644
--- a/regress/expected/cypher_match.out
+++ b/regress/expected/cypher_match.out
@@ -1256,6 +1256,25 @@ SELECT * FROM cypher('cypher_match', $$
  "nobody"   |               |            | "anybody"  |               | 
 (12 rows)
 
+-- Tests to catch match following optional match logic
+-- this syntax is invalid in cypher
+SELECT * FROM cypher('cypher_match', $$
+	OPTIONAL MATCH (n)
+    MATCH (m)
+    RETURN n,m
+ $$) AS (n agtype, m agtype);
+ERROR:  MATCH cannot follow OPTIONAL MATCH
+LINE 1: SELECT * FROM cypher('cypher_match', $$
+                                              ^
+SELECT * FROM cypher('cypher_match', $$
+	MATCH (n)
+	OPTIONAL MATCH (m)
+    MATCH (o)
+    RETURN n,m
+ $$) AS (n agtype, m agtype);
+ERROR:  MATCH cannot follow OPTIONAL MATCH
+LINE 1: SELECT * FROM cypher('cypher_match', $$
+                                              ^
 --
 -- Tests retrieving Var from some parent's cpstate during transformation
 --
diff --git a/regress/sql/cypher_match.sql b/regress/sql/cypher_match.sql
index 70f638df..344453e0 100644
--- a/regress/sql/cypher_match.sql
+++ b/regress/sql/cypher_match.sql
@@ -621,6 +621,20 @@ SELECT * FROM cypher('cypher_match', $$
     ORDER BY n, p, m, q
  $$) AS (n agtype, r agtype, p agtype, m agtype, s agtype, q agtype);
 
+-- Tests to catch match following optional match logic
+-- this syntax is invalid in cypher
+SELECT * FROM cypher('cypher_match', $$
+	OPTIONAL MATCH (n)
+    MATCH (m)
+    RETURN n,m
+ $$) AS (n agtype, m agtype);
+
+SELECT * FROM cypher('cypher_match', $$
+	MATCH (n)
+	OPTIONAL MATCH (m)
+    MATCH (o)
+    RETURN n,m
+ $$) AS (n agtype, m agtype);
 
 --
 -- Tests retrieving Var from some parent's cpstate during transformation
diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c
index e1e6c94b..294ebb06 100644
--- a/src/backend/parser/cypher_clause.c
+++ b/src/backend/parser/cypher_clause.c
@@ -2508,6 +2508,23 @@ static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate,
     query = makeNode(Query);
     query->commandType = CMD_SELECT;
 
+    if(self->optional == true && clause->next)
+    {
+        cypher_clause *next = clause->next;
+        if (is_ag_node(next->self, cypher_match))
+        {
+            cypher_match *next_self = (cypher_match *)next->self;
+            if (!next_self->optional)
+            {
+                ereport(ERROR,
+                        (errcode(ERRCODE_SYNTAX_ERROR),
+                         errmsg("MATCH cannot follow OPTIONAL MATCH"),
+                         parser_errposition(pstate,
+                                            exprLocation((Node *) next_self))));
+            }
+        }
+    }
+
     // If there is no previous clause, transform to a general MATCH clause.
     if (self->optional == true && clause->prev != NULL)
     {


[age] 03/19: Update Go driver CI to run on every PR/commit (#683)

Posted by jg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 6a3fa926e0069bdee11c1234e306f2d677148ad4
Author: Muhammad Taha Naveed <85...@users.noreply.github.com>
AuthorDate: Thu Feb 16 22:45:02 2023 +0500

    Update Go driver CI to run on every PR/commit (#683)
    
    - Removed path directive
    - Included PG11 as trigger branch
---
 .github/workflows/go-driver.yml | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/.github/workflows/go-driver.yml b/.github/workflows/go-driver.yml
index a2b543cd..fbf2cec9 100644
--- a/.github/workflows/go-driver.yml
+++ b/.github/workflows/go-driver.yml
@@ -2,14 +2,10 @@ name: Go Driver Tests
 
 on:
   push:
-    branches: [ "master" ]
-    paths: 
-      - 'drivers/golang/**'
+    branches: [ "master", PG11 ]
 
   pull_request:
-    branches: [ "master" ]
-    paths: 
-      - 'drivers/golang/**'
+    branches: [ "master", PG11 ]
 
 jobs:
   build:


[age] 16/19: Fix issue #733 - create_complete_graph() terminates the server (#734)

Posted by jg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 89feda14d4634aa1793aff87520b66530febd485
Author: Muhammad Taha Naveed <mn...@seecs.edu.pk>
AuthorDate: Tue Mar 7 03:26:44 2023 +0500

    Fix issue #733 - create_complete_graph() terminates the server (#734)
    
    - A vertex and  an edge cannot have the same label within the same
      graph, so added a check for that.
    
    - insert_simple_vertex() and insert_simple_edge() functions were not
      checking for the kind of label. Since a vertex cannot be
      inserted into a label relation of edge and vice versa, this was
      causing the server termination. Added a check for that.
    
    - Added regression tests.
---
 regress/expected/graph_generation.out |  8 ++++++++
 regress/sql/graph_generation.sql      |  8 ++++++++
 src/backend/catalog/ag_label.c        | 15 +++++++++++++++
 src/backend/utils/graph_generation.c  | 14 ++++++++++++--
 src/backend/utils/load/age_load.c     | 13 +++++++++++++
 src/include/catalog/ag_label.h        |  1 +
 6 files changed, 57 insertions(+), 2 deletions(-)

diff --git a/regress/expected/graph_generation.out b/regress/expected/graph_generation.out
index b9ed65b1..235052a0 100644
--- a/regress/expected/graph_generation.out
+++ b/regress/expected/graph_generation.out
@@ -80,12 +80,17 @@ NOTICE:  ELabel "edges" has been created
  
 (1 row)
 
+-- SHOULD FAIL
 SELECT * FROM create_complete_graph('gp3',5, NULL);
 ERROR:  edge label can not be NULL
 SELECT * FROM create_complete_graph('gp4',NULL,NULL);
 ERROR:  number of nodes can not be NULL
 SELECT * FROM create_complete_graph(NULL,NULL,NULL);
 ERROR:  graph name can not be NULL
+-- Should error out because same labels are used for both vertices and edges
+SELECT * FROM create_complete_graph('gp5',5,'label','label');
+ERROR:  vertex and edge label can not be same
+-- DROPPING GRAPHS
 SELECT drop_graph('gp1', true);
 NOTICE:  drop cascades to 4 other objects
 DETAIL:  drop cascades to table gp1._ag_label_vertex
@@ -195,6 +200,9 @@ SELECT * FROM age_create_barbell_graph('gp4',NULL,0,'vertices',NULL,'edges',NULL
 ERROR:  Graph size cannot be NULL or lower than 3
 SELECT * FROM age_create_barbell_graph('gp5',5,0,'vertices',NULL,NULL,NULL);
 ERROR:  edge label can not be NULL
+-- Should error out because same labels are used for both vertices and edges
+SELECT * FROM age_create_barbell_graph('gp6',5,10,'label',NULL,'label',NULL);
+ERROR:  vertex and edge label can not be same
 -- DROPPING GRAPHS
 SELECT drop_graph('gp1', true);
 NOTICE:  drop cascades to 4 other objects
diff --git a/regress/sql/graph_generation.sql b/regress/sql/graph_generation.sql
index 32fa56dd..e9ee8ea8 100644
--- a/regress/sql/graph_generation.sql
+++ b/regress/sql/graph_generation.sql
@@ -36,12 +36,17 @@ SELECT COUNT(*) FROM gp1."vertices";
 
 SELECT * FROM create_complete_graph('gp2',5,'edges');
 
+-- SHOULD FAIL
 SELECT * FROM create_complete_graph('gp3',5, NULL);
 
 SELECT * FROM create_complete_graph('gp4',NULL,NULL);
 
 SELECT * FROM create_complete_graph(NULL,NULL,NULL);
 
+-- Should error out because same labels are used for both vertices and edges
+SELECT * FROM create_complete_graph('gp5',5,'label','label');
+
+-- DROPPING GRAPHS
 SELECT drop_graph('gp1', true);
 SELECT drop_graph('gp2', true);
 
@@ -68,6 +73,9 @@ SELECT * FROM age_create_barbell_graph('gp3',5,NULL,'vertices',NULL,'edges',NULL
 SELECT * FROM age_create_barbell_graph('gp4',NULL,0,'vertices',NULL,'edges',NULL);
 SELECT * FROM age_create_barbell_graph('gp5',5,0,'vertices',NULL,NULL,NULL);
 
+-- Should error out because same labels are used for both vertices and edges
+SELECT * FROM age_create_barbell_graph('gp6',5,10,'label',NULL,'label',NULL);
+
 -- DROPPING GRAPHS
 SELECT drop_graph('gp1', true);
 SELECT drop_graph('gp2', true);
diff --git a/src/backend/catalog/ag_label.c b/src/backend/catalog/ag_label.c
index cd8d0bab..41a11abe 100644
--- a/src/backend/catalog/ag_label.c
+++ b/src/backend/catalog/ag_label.c
@@ -170,6 +170,21 @@ char *get_label_relation_name(const char *label_name, Oid label_graph)
     return get_rel_name(get_label_relation(label_name, label_graph));
 }
 
+char get_label_kind(const char *label_name, Oid label_graph)
+{
+    label_cache_data *cache_data;
+
+    cache_data = search_label_name_graph_cache(label_name, label_graph);
+    if (cache_data)
+    {
+        return cache_data->kind;
+    }
+    else
+    {
+        return INVALID_LABEL_ID;
+    }
+}
+
 PG_FUNCTION_INFO_V1(_label_name);
 
 /*
diff --git a/src/backend/utils/graph_generation.c b/src/backend/utils/graph_generation.c
index b40ef07e..e1a36344 100644
--- a/src/backend/utils/graph_generation.c
+++ b/src/backend/utils/graph_generation.c
@@ -142,6 +142,18 @@ Datum create_complete_graph(PG_FUNCTION_ARGS)
     vtx_name_str = AG_DEFAULT_LABEL_VERTEX;
     edge_name_str = NameStr(*edge_label_name);
 
+    if (!PG_ARGISNULL(3))
+    {
+        vtx_label_name = PG_GETARG_NAME(3);
+        vtx_name_str = NameStr(*vtx_label_name);
+        
+        // Check if vertex and edge label are same
+        if (strcmp(vtx_name_str, edge_name_str) == 0)
+        {
+            ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                    errmsg("vertex and edge label can not be same")));
+        }
+    }
 
     if (!graph_exists(graph_name_str))
     {
@@ -154,8 +166,6 @@ Datum create_complete_graph(PG_FUNCTION_ARGS)
     
     if (!PG_ARGISNULL(3))
     {
-        vtx_label_name = PG_GETARG_NAME(3);
-        vtx_name_str = NameStr(*vtx_label_name);
         // Check if label with the input name already exists
         if (!label_exists(vtx_name_str, graph_id))
         {
diff --git a/src/backend/utils/load/age_load.c b/src/backend/utils/load/age_load.c
index cbdf1585..d9819a30 100644
--- a/src/backend/utils/load/age_load.c
+++ b/src/backend/utils/load/age_load.c
@@ -148,6 +148,12 @@ void insert_edge_simple(Oid graph_id, char* label_name, graphid edge_id,
     Relation label_relation;
     HeapTuple tuple;
 
+    // Check if label provided exists as vertex label, then throw error
+    if (get_label_kind(label_name, graph_id) == LABEL_KIND_VERTEX)
+    {
+        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                        errmsg("label %s already exists as vertex label", label_name)));
+    }
 
     values[0] = GRAPHID_GET_DATUM(edge_id);
     values[1] = GRAPHID_GET_DATUM(start_id);
@@ -176,6 +182,13 @@ void insert_vertex_simple(Oid graph_id, char* label_name,
     Relation label_relation;
     HeapTuple tuple;
 
+    // Check if label provided exists as edge label, then throw error
+    if (get_label_kind(label_name, graph_id) == LABEL_KIND_EDGE)
+    {
+        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                        errmsg("label %s already exists as edge label", label_name)));
+    }
+
     values[0] = GRAPHID_GET_DATUM(vertex_id);
     values[1] = AGTYPE_P_GET_DATUM((vertex_properties));
 
diff --git a/src/include/catalog/ag_label.h b/src/include/catalog/ag_label.h
index f60a038b..39ab5c97 100644
--- a/src/include/catalog/ag_label.h
+++ b/src/include/catalog/ag_label.h
@@ -78,6 +78,7 @@ Oid get_label_oid(const char *label_name, Oid label_graph);
 int32 get_label_id(const char *label_name, Oid label_graph);
 Oid get_label_relation(const char *label_name, Oid label_graph);
 char *get_label_relation_name(const char *label_name, Oid label_graph);
+char get_label_kind(const char *label_name, Oid label_graph);
 
 bool label_id_exists(Oid label_graph, int32 label_id);
 RangeVar *get_label_range_var(char *graph_name, Oid graph_oid, char *label_name);


[age] 14/19: Implemented CI test for nodejs driver (#720)

Posted by jg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit ef82b9dd641c3dde0ff92ed6a0a1d33b66cfb448
Author: Muhammad Taha Naveed <mn...@seecs.edu.pk>
AuthorDate: Fri Mar 3 00:22:46 2023 +0500

    Implemented CI test for nodejs driver (#720)
    
    * Modify nodejs driver tests
    
    - Modified connection params to connect with running docker instance.
    - Fixed failing tests by modifying the expected outcomes.
    
    * Include asf header as key/value to avoid EJSONPARSE error
    
    - "//" key will never be used by npm for any purpose,
      and is reserved for comments.
    
    * Implement CI test for nodejs driver
---
 .github/workflows/nodejs-driver.yaml | 36 ++++++++++++++++++++++
 drivers/docker-compose.yml           |  2 +-
 drivers/nodejs/package.json          | 37 +++++++++++-----------
 drivers/nodejs/test/index.test.ts    | 59 ++++++++++++++++++++++++------------
 4 files changed, 94 insertions(+), 40 deletions(-)

diff --git a/.github/workflows/nodejs-driver.yaml b/.github/workflows/nodejs-driver.yaml
new file mode 100644
index 00000000..f812e2c3
--- /dev/null
+++ b/.github/workflows/nodejs-driver.yaml
@@ -0,0 +1,36 @@
+name: Nodejs Driver Tests
+
+on:
+  push:
+    branches: [ "master", "PG11" ]
+
+  pull_request:
+    branches: [ "master", "PG11" ]
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+
+    defaults:
+      run:
+        working-directory: drivers/nodejs/
+  
+    steps:
+    - uses: actions/checkout@v3
+    
+    - name: Run apache/age docker image
+      run: docker-compose up -d
+
+    - name: Set up Node
+      uses: actions/setup-node@v3
+      with:
+        node-version: latest
+
+    - name: Install dependencies
+      run: npm install
+
+    - name: Build
+      run: npm run build
+
+    - name: Test
+      run: npm test
\ No newline at end of file
diff --git a/drivers/docker-compose.yml b/drivers/docker-compose.yml
index 668eb19d..8bdf0031 100644
--- a/drivers/docker-compose.yml
+++ b/drivers/docker-compose.yml
@@ -1,7 +1,7 @@
 version: "3.3"
 services:
   db:
-    image: apache/age
+    image: apache/age:latest
     environment:
       - POSTGRES_USER=postgres
       - POSTGRES_PASSWORD=agens
diff --git a/drivers/nodejs/package.json b/drivers/nodejs/package.json
index db385eef..9f88bc2b 100644
--- a/drivers/nodejs/package.json
+++ b/drivers/nodejs/package.json
@@ -1,23 +1,22 @@
-/*
- * 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.
- */
-
 {
+  "//": [
+    "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."
+  ],
   "name": "pg-age",
   "version": "1.0.0-alpha",
   "main": "dist/index.js",
diff --git a/drivers/nodejs/test/index.test.ts b/drivers/nodejs/test/index.test.ts
index 60f9e2fe..ea386dda 100644
--- a/drivers/nodejs/test/index.test.ts
+++ b/drivers/nodejs/test/index.test.ts
@@ -24,8 +24,8 @@ const config = {
   user: 'postgres',
   host: '127.0.0.1',
   database: 'postgres',
-  password: 'postgres',
-  port: 25432
+  password: 'agens',
+  port: 5432
 }
 
 const testGraphName = 'age-test'
@@ -59,25 +59,44 @@ describe('Pre-connected Connection', () => {
             $$) as (a agtype);
         `)!
     expect(results.rows).toStrictEqual(
-      [{
-        a: {
-          id: 844424930131969,
-          label: 'Part',
-          properties: { part_num: '123' }
+      [
+        {
+          a : new Map(Object.entries({
+            id: 844424930131969,
+            label: 'Part',
+            properties: new Map(Object.entries({
+              part_num: '123'
+            }))
+          })),
+        },
+        {
+          a : new Map(Object.entries({
+            id: 844424930131970,
+            label: 'Part',
+            properties: new Map(Object.entries({
+              part_num: '345'
+            }))
+          })),
+        },
+        {
+          a : new Map(Object.entries({
+            id: 844424930131971,
+            label: 'Part',
+            properties: new Map(Object.entries({
+              part_num: '456'
+            }))
+          })),
+        },
+        {
+          a : new Map(Object.entries({
+            id: 844424930131972,
+            label: 'Part',
+            properties: new Map(Object.entries({
+              part_num: '789'
+            }))
+          })),
         }
-      }, {
-        a: {
-          id: 844424930131970,
-          label: 'Part',
-          properties: { part_num: '345' }
-        }
-      }, {
-        a: {
-          id: 844424930131971,
-          label: 'Part',
-          properties: { part_num: '456' }
-        }
-      }, { a: { id: 844424930131972, label: 'Part', properties: { part_num: '789' } } }]
+      ]
     )
   })
 })


[age] 05/19: Update python driver CI to run on every PR/commit (#684)

Posted by jg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 5df5d440d71de5ccbf53cac2b20f7709115cf9f8
Author: Muhammad Taha Naveed <85...@users.noreply.github.com>
AuthorDate: Fri Feb 17 06:08:08 2023 +0500

    Update python driver CI to run on every PR/commit (#684)
    
    - Removed path directive
    - Included PG11 as trigger branch
---
 .github/workflows/python-driver.yaml | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/.github/workflows/python-driver.yaml b/.github/workflows/python-driver.yaml
index fb6ca3b3..720e8c22 100644
--- a/.github/workflows/python-driver.yaml
+++ b/.github/workflows/python-driver.yaml
@@ -2,14 +2,10 @@ name: Python Driver Tests
 
 on:
   push:
-    branches: [ "master" ]
-    paths: 
-      - 'drivers/python/**'
+    branches: [ "master", "PG11" ]
       
   pull_request:
-    branches: [ "master" ]
-    paths: 
-      - 'drivers/python/**'
+    branches: [ "master", "PG11" ]
 
 jobs:
   build:


[age] 07/19: UPDATED Python Driver ANTLR 4.9.3 -> 4.11.1 (#706)

Posted by jg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 73353c5a40ad12249302b423eedf2767ddc30f7c
Author: Arunabh Das <pa...@hotmail.com>
AuthorDate: Thu Feb 23 17:54:29 2023 +0800

    UPDATED Python Driver ANTLR 4.9.3 -> 4.11.1 (#706)
---
 .gitignore                               |   2 +
 drivers/python/age/gen/AgtypeLexer.py    | 153 +++++++++++++++----------------
 drivers/python/age/gen/AgtypeListener.py |   3 +-
 drivers/python/age/gen/AgtypeParser.py   | 100 ++++++++++----------
 drivers/python/age/gen/AgtypeVisitor.py  |   3 +-
 drivers/python/antlr/README.md           |  14 ++-
 drivers/python/requirements.txt          | Bin 158 -> 160 bytes
 drivers/python/setup.py                  |   2 +-
 8 files changed, 138 insertions(+), 139 deletions(-)

diff --git a/.gitignore b/.gitignore
index 78e99125..660286b0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,5 @@ build.sh
 .idea
 .deps
 .DS_Store
+*.tokens
+*.interp
\ No newline at end of file
diff --git a/drivers/python/age/gen/AgtypeLexer.py b/drivers/python/age/gen/AgtypeLexer.py
index 543bee28..54f19c7d 100644
--- a/drivers/python/age/gen/AgtypeLexer.py
+++ b/drivers/python/age/gen/AgtypeLexer.py
@@ -12,7 +12,8 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-# Generated from Agtype.g4 by ANTLR 4.9.2
+# Generated from ../Agtype.g4 by ANTLR 4.11.1
+
 from antlr4 import *
 from io import StringIO
 import sys
@@ -22,86 +23,76 @@ else:
     from typing.io import TextIO
 
 
-
 def serializedATN():
-    with StringIO() as buf:
-        buf.write("\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\2\25")
-        buf.write("\u00b9\b\1\4\2\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7")
-        buf.write("\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4\13\t\13\4\f\t\f\4\r\t\r")
-        buf.write("\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22\t\22\4\23")
-        buf.write("\t\23\4\24\t\24\4\25\t\25\4\26\t\26\4\27\t\27\4\30\t\30")
-        buf.write("\4\31\t\31\4\32\t\32\4\33\t\33\3\2\3\2\3\2\3\2\3\2\3\3")
-        buf.write("\3\3\3\3\3\3\3\3\3\3\3\4\3\4\3\4\3\4\3\4\3\5\3\5\3\6\3")
-        buf.write("\6\3\7\3\7\3\b\3\b\3\t\3\t\3\n\3\n\3\13\3\13\3\13\3\f")
-        buf.write("\3\f\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\16\3\16\3\16")
-        buf.write("\3\16\3\17\3\17\7\17h\n\17\f\17\16\17k\13\17\3\20\3\20")
-        buf.write("\3\20\7\20p\n\20\f\20\16\20s\13\20\3\20\3\20\3\21\3\21")
-        buf.write("\3\21\5\21z\n\21\3\22\3\22\3\22\3\22\3\22\3\22\3\23\3")
-        buf.write("\23\3\24\3\24\3\25\5\25\u0087\n\25\3\25\3\25\3\26\3\26")
-        buf.write("\3\26\7\26\u008e\n\26\f\26\16\26\u0091\13\26\5\26\u0093")
-        buf.write("\n\26\3\27\5\27\u0096\n\27\3\27\3\27\3\27\3\30\5\30\u009c")
-        buf.write("\n\30\3\30\3\30\5\30\u00a0\n\30\3\30\3\30\3\31\3\31\6")
-        buf.write("\31\u00a6\n\31\r\31\16\31\u00a7\3\32\3\32\5\32\u00ac\n")
-        buf.write("\32\3\32\6\32\u00af\n\32\r\32\16\32\u00b0\3\33\6\33\u00b4")
-        buf.write("\n\33\r\33\16\33\u00b5\3\33\3\33\2\2\34\3\3\5\4\7\5\t")
-        buf.write("\6\13\7\r\b\17\t\21\n\23\13\25\f\27\r\31\16\33\17\35\20")
-        buf.write("\37\21!\2#\2%\2\'\2)\22+\2-\23/\24\61\2\63\2\65\25\3\2")
-        buf.write("\f\5\2C\\aac|\7\2&&\62;C\\aac|\n\2$$\61\61^^ddhhppttv")
-        buf.write("v\5\2\62;CHch\5\2\2!$$^^\3\2\63;\3\2\62;\4\2GGgg\4\2-")
-        buf.write("-//\5\2\13\f\17\17\"\"\2\u00bf\2\3\3\2\2\2\2\5\3\2\2\2")
-        buf.write("\2\7\3\2\2\2\2\t\3\2\2\2\2\13\3\2\2\2\2\r\3\2\2\2\2\17")
-        buf.write("\3\2\2\2\2\21\3\2\2\2\2\23\3\2\2\2\2\25\3\2\2\2\2\27\3")
-        buf.write("\2\2\2\2\31\3\2\2\2\2\33\3\2\2\2\2\35\3\2\2\2\2\37\3\2")
-        buf.write("\2\2\2)\3\2\2\2\2-\3\2\2\2\2/\3\2\2\2\2\65\3\2\2\2\3\67")
-        buf.write("\3\2\2\2\5<\3\2\2\2\7B\3\2\2\2\tG\3\2\2\2\13I\3\2\2\2")
-        buf.write("\rK\3\2\2\2\17M\3\2\2\2\21O\3\2\2\2\23Q\3\2\2\2\25S\3")
-        buf.write("\2\2\2\27V\3\2\2\2\31X\3\2\2\2\33a\3\2\2\2\35e\3\2\2\2")
-        buf.write("\37l\3\2\2\2!v\3\2\2\2#{\3\2\2\2%\u0081\3\2\2\2\'\u0083")
-        buf.write("\3\2\2\2)\u0086\3\2\2\2+\u0092\3\2\2\2-\u0095\3\2\2\2")
-        buf.write("/\u009b\3\2\2\2\61\u00a3\3\2\2\2\63\u00a9\3\2\2\2\65\u00b3")
-        buf.write("\3\2\2\2\678\7v\2\289\7t\2\29:\7w\2\2:;\7g\2\2;\4\3\2")
-        buf.write("\2\2<=\7h\2\2=>\7c\2\2>?\7n\2\2?@\7u\2\2@A\7g\2\2A\6\3")
-        buf.write("\2\2\2BC\7p\2\2CD\7w\2\2DE\7n\2\2EF\7n\2\2F\b\3\2\2\2")
-        buf.write("GH\7}\2\2H\n\3\2\2\2IJ\7.\2\2J\f\3\2\2\2KL\7\177\2\2L")
-        buf.write("\16\3\2\2\2MN\7<\2\2N\20\3\2\2\2OP\7]\2\2P\22\3\2\2\2")
-        buf.write("QR\7_\2\2R\24\3\2\2\2ST\7<\2\2TU\7<\2\2U\26\3\2\2\2VW")
-        buf.write("\7/\2\2W\30\3\2\2\2XY\7K\2\2YZ\7p\2\2Z[\7h\2\2[\\\7k\2")
-        buf.write("\2\\]\7p\2\2]^\7k\2\2^_\7v\2\2_`\7{\2\2`\32\3\2\2\2ab")
-        buf.write("\7P\2\2bc\7c\2\2cd\7P\2\2d\34\3\2\2\2ei\t\2\2\2fh\t\3")
-        buf.write("\2\2gf\3\2\2\2hk\3\2\2\2ig\3\2\2\2ij\3\2\2\2j\36\3\2\2")
-        buf.write("\2ki\3\2\2\2lq\7$\2\2mp\5!\21\2np\5\'\24\2om\3\2\2\2o")
-        buf.write("n\3\2\2\2ps\3\2\2\2qo\3\2\2\2qr\3\2\2\2rt\3\2\2\2sq\3")
-        buf.write("\2\2\2tu\7$\2\2u \3\2\2\2vy\7^\2\2wz\t\4\2\2xz\5#\22\2")
-        buf.write("yw\3\2\2\2yx\3\2\2\2z\"\3\2\2\2{|\7w\2\2|}\5%\23\2}~\5")
-        buf.write("%\23\2~\177\5%\23\2\177\u0080\5%\23\2\u0080$\3\2\2\2\u0081")
-        buf.write("\u0082\t\5\2\2\u0082&\3\2\2\2\u0083\u0084\n\6\2\2\u0084")
-        buf.write("(\3\2\2\2\u0085\u0087\7/\2\2\u0086\u0085\3\2\2\2\u0086")
-        buf.write("\u0087\3\2\2\2\u0087\u0088\3\2\2\2\u0088\u0089\5+\26\2")
-        buf.write("\u0089*\3\2\2\2\u008a\u0093\7\62\2\2\u008b\u008f\t\7\2")
-        buf.write("\2\u008c\u008e\t\b\2\2\u008d\u008c\3\2\2\2\u008e\u0091")
-        buf.write("\3\2\2\2\u008f\u008d\3\2\2\2\u008f\u0090\3\2\2\2\u0090")
-        buf.write("\u0093\3\2\2\2\u0091\u008f\3\2\2\2\u0092\u008a\3\2\2\2")
-        buf.write("\u0092\u008b\3\2\2\2\u0093,\3\2\2\2\u0094\u0096\7/\2\2")
-        buf.write("\u0095\u0094\3\2\2\2\u0095\u0096\3\2\2\2\u0096\u0097\3")
-        buf.write("\2\2\2\u0097\u0098\5+\26\2\u0098\u0099\5\61\31\2\u0099")
-        buf.write(".\3\2\2\2\u009a\u009c\7/\2\2\u009b\u009a\3\2\2\2\u009b")
-        buf.write("\u009c\3\2\2\2\u009c\u009d\3\2\2\2\u009d\u009f\5+\26\2")
-        buf.write("\u009e\u00a0\5\61\31\2\u009f\u009e\3\2\2\2\u009f\u00a0")
-        buf.write("\3\2\2\2\u00a0\u00a1\3\2\2\2\u00a1\u00a2\5\63\32\2\u00a2")
-        buf.write("\60\3\2\2\2\u00a3\u00a5\7\60\2\2\u00a4\u00a6\t\b\2\2\u00a5")
-        buf.write("\u00a4\3\2\2\2\u00a6\u00a7\3\2\2\2\u00a7\u00a5\3\2\2\2")
-        buf.write("\u00a7\u00a8\3\2\2\2\u00a8\62\3\2\2\2\u00a9\u00ab\t\t")
-        buf.write("\2\2\u00aa\u00ac\t\n\2\2\u00ab\u00aa\3\2\2\2\u00ab\u00ac")
-        buf.write("\3\2\2\2\u00ac\u00ae\3\2\2\2\u00ad\u00af\t\b\2\2\u00ae")
-        buf.write("\u00ad\3\2\2\2\u00af\u00b0\3\2\2\2\u00b0\u00ae\3\2\2\2")
-        buf.write("\u00b0\u00b1\3\2\2\2\u00b1\64\3\2\2\2\u00b2\u00b4\t\13")
-        buf.write("\2\2\u00b3\u00b2\3\2\2\2\u00b4\u00b5\3\2\2\2\u00b5\u00b3")
-        buf.write("\3\2\2\2\u00b5\u00b6\3\2\2\2\u00b6\u00b7\3\2\2\2\u00b7")
-        buf.write("\u00b8\b\33\2\2\u00b8\66\3\2\2\2\21\2ioqy\u0086\u008f")
-        buf.write("\u0092\u0095\u009b\u009f\u00a7\u00ab\u00b0\u00b5\3\b\2")
-        buf.write("\2")
-        return buf.getvalue()
-
+    return [
+        4,0,19,183,6,-1,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,
+        2,6,7,6,2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,
+        13,7,13,2,14,7,14,2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19,7,
+        19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,2,24,7,24,2,25,7,25,1,
+        0,1,0,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,2,1,2,1,2,1,2,1,
+        3,1,3,1,4,1,4,1,5,1,5,1,6,1,6,1,7,1,7,1,8,1,8,1,9,1,9,1,9,1,10,1,
+        10,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,12,1,12,1,12,1,
+        12,1,13,1,13,5,13,102,8,13,10,13,12,13,105,9,13,1,14,1,14,1,14,5,
+        14,110,8,14,10,14,12,14,113,9,14,1,14,1,14,1,15,1,15,1,15,3,15,120,
+        8,15,1,16,1,16,1,16,1,16,1,16,1,16,1,17,1,17,1,18,1,18,1,19,3,19,
+        133,8,19,1,19,1,19,1,20,1,20,1,20,5,20,140,8,20,10,20,12,20,143,
+        9,20,3,20,145,8,20,1,21,3,21,148,8,21,1,21,1,21,1,21,1,22,3,22,154,
+        8,22,1,22,1,22,3,22,158,8,22,1,22,1,22,1,23,1,23,4,23,164,8,23,11,
+        23,12,23,165,1,24,1,24,3,24,170,8,24,1,24,4,24,173,8,24,11,24,12,
+        24,174,1,25,4,25,178,8,25,11,25,12,25,179,1,25,1,25,0,0,26,1,1,3,
+        2,5,3,7,4,9,5,11,6,13,7,15,8,17,9,19,10,21,11,23,12,25,13,27,14,
+        29,15,31,0,33,0,35,0,37,0,39,16,41,0,43,17,45,18,47,0,49,0,51,19,
+        1,0,10,3,0,65,90,95,95,97,122,5,0,36,36,48,57,65,90,95,95,97,122,
+        8,0,34,34,47,47,92,92,98,98,102,102,110,110,114,114,116,116,3,0,
+        48,57,65,70,97,102,3,0,0,31,34,34,92,92,1,0,49,57,1,0,48,57,2,0,
+        69,69,101,101,2,0,43,43,45,45,3,0,9,10,13,13,32,32,189,0,1,1,0,0,
+        0,0,3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,1,0,0,0,0,11,1,0,0,0,0,
+        13,1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0,
+        23,1,0,0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,29,1,0,0,0,0,39,1,0,0,0,0,
+        43,1,0,0,0,0,45,1,0,0,0,0,51,1,0,0,0,1,53,1,0,0,0,3,58,1,0,0,0,5,
+        64,1,0,0,0,7,69,1,0,0,0,9,71,1,0,0,0,11,73,1,0,0,0,13,75,1,0,0,0,
+        15,77,1,0,0,0,17,79,1,0,0,0,19,81,1,0,0,0,21,84,1,0,0,0,23,86,1,
+        0,0,0,25,95,1,0,0,0,27,99,1,0,0,0,29,106,1,0,0,0,31,116,1,0,0,0,
+        33,121,1,0,0,0,35,127,1,0,0,0,37,129,1,0,0,0,39,132,1,0,0,0,41,144,
+        1,0,0,0,43,147,1,0,0,0,45,153,1,0,0,0,47,161,1,0,0,0,49,167,1,0,
+        0,0,51,177,1,0,0,0,53,54,5,116,0,0,54,55,5,114,0,0,55,56,5,117,0,
+        0,56,57,5,101,0,0,57,2,1,0,0,0,58,59,5,102,0,0,59,60,5,97,0,0,60,
+        61,5,108,0,0,61,62,5,115,0,0,62,63,5,101,0,0,63,4,1,0,0,0,64,65,
+        5,110,0,0,65,66,5,117,0,0,66,67,5,108,0,0,67,68,5,108,0,0,68,6,1,
+        0,0,0,69,70,5,123,0,0,70,8,1,0,0,0,71,72,5,44,0,0,72,10,1,0,0,0,
+        73,74,5,125,0,0,74,12,1,0,0,0,75,76,5,58,0,0,76,14,1,0,0,0,77,78,
+        5,91,0,0,78,16,1,0,0,0,79,80,5,93,0,0,80,18,1,0,0,0,81,82,5,58,0,
+        0,82,83,5,58,0,0,83,20,1,0,0,0,84,85,5,45,0,0,85,22,1,0,0,0,86,87,
+        5,73,0,0,87,88,5,110,0,0,88,89,5,102,0,0,89,90,5,105,0,0,90,91,5,
+        110,0,0,91,92,5,105,0,0,92,93,5,116,0,0,93,94,5,121,0,0,94,24,1,
+        0,0,0,95,96,5,78,0,0,96,97,5,97,0,0,97,98,5,78,0,0,98,26,1,0,0,0,
+        99,103,7,0,0,0,100,102,7,1,0,0,101,100,1,0,0,0,102,105,1,0,0,0,103,
+        101,1,0,0,0,103,104,1,0,0,0,104,28,1,0,0,0,105,103,1,0,0,0,106,111,
+        5,34,0,0,107,110,3,31,15,0,108,110,3,37,18,0,109,107,1,0,0,0,109,
+        108,1,0,0,0,110,113,1,0,0,0,111,109,1,0,0,0,111,112,1,0,0,0,112,
+        114,1,0,0,0,113,111,1,0,0,0,114,115,5,34,0,0,115,30,1,0,0,0,116,
+        119,5,92,0,0,117,120,7,2,0,0,118,120,3,33,16,0,119,117,1,0,0,0,119,
+        118,1,0,0,0,120,32,1,0,0,0,121,122,5,117,0,0,122,123,3,35,17,0,123,
+        124,3,35,17,0,124,125,3,35,17,0,125,126,3,35,17,0,126,34,1,0,0,0,
+        127,128,7,3,0,0,128,36,1,0,0,0,129,130,8,4,0,0,130,38,1,0,0,0,131,
+        133,5,45,0,0,132,131,1,0,0,0,132,133,1,0,0,0,133,134,1,0,0,0,134,
+        135,3,41,20,0,135,40,1,0,0,0,136,145,5,48,0,0,137,141,7,5,0,0,138,
+        140,7,6,0,0,139,138,1,0,0,0,140,143,1,0,0,0,141,139,1,0,0,0,141,
+        142,1,0,0,0,142,145,1,0,0,0,143,141,1,0,0,0,144,136,1,0,0,0,144,
+        137,1,0,0,0,145,42,1,0,0,0,146,148,5,45,0,0,147,146,1,0,0,0,147,
+        148,1,0,0,0,148,149,1,0,0,0,149,150,3,41,20,0,150,151,3,47,23,0,
+        151,44,1,0,0,0,152,154,5,45,0,0,153,152,1,0,0,0,153,154,1,0,0,0,
+        154,155,1,0,0,0,155,157,3,41,20,0,156,158,3,47,23,0,157,156,1,0,
+        0,0,157,158,1,0,0,0,158,159,1,0,0,0,159,160,3,49,24,0,160,46,1,0,
+        0,0,161,163,5,46,0,0,162,164,7,6,0,0,163,162,1,0,0,0,164,165,1,0,
+        0,0,165,163,1,0,0,0,165,166,1,0,0,0,166,48,1,0,0,0,167,169,7,7,0,
+        0,168,170,7,8,0,0,169,168,1,0,0,0,169,170,1,0,0,0,170,172,1,0,0,
+        0,171,173,7,6,0,0,172,171,1,0,0,0,173,174,1,0,0,0,174,172,1,0,0,
+        0,174,175,1,0,0,0,175,50,1,0,0,0,176,178,7,9,0,0,177,176,1,0,0,0,
+        178,179,1,0,0,0,179,177,1,0,0,0,179,180,1,0,0,0,180,181,1,0,0,0,
+        181,182,6,25,0,0,182,52,1,0,0,0,15,0,103,109,111,119,132,141,144,
+        147,153,157,165,169,174,179,1,6,0,0
+    ]
 
 class AgtypeLexer(Lexer):
 
@@ -151,7 +142,7 @@ class AgtypeLexer(Lexer):
 
     def __init__(self, input=None, output:TextIO = sys.stdout):
         super().__init__(input, output)
-        self.checkVersion("4.9.2")
+        self.checkVersion("4.11.1")
         self._interp = LexerATNSimulator(self, self.atn, self.decisionsToDFA, PredictionContextCache())
         self._actions = None
         self._predicates = None
diff --git a/drivers/python/age/gen/AgtypeListener.py b/drivers/python/age/gen/AgtypeListener.py
index 15016bf9..6e60da42 100644
--- a/drivers/python/age/gen/AgtypeListener.py
+++ b/drivers/python/age/gen/AgtypeListener.py
@@ -12,7 +12,8 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-# Generated from Agtype.g4 by ANTLR 4.9.2
+# Generated from ../Agtype.g4 by ANTLR 4.11.1
+
 from antlr4 import *
 if __name__ is not None and "." in __name__:
     from .AgtypeParser import AgtypeParser
diff --git a/drivers/python/age/gen/AgtypeParser.py b/drivers/python/age/gen/AgtypeParser.py
index ff98746a..daaf578e 100644
--- a/drivers/python/age/gen/AgtypeParser.py
+++ b/drivers/python/age/gen/AgtypeParser.py
@@ -12,8 +12,9 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-# Generated from Agtype.g4 by ANTLR 4.9.2
+# Generated from ../Agtype.g4 by ANTLR 4.11.1
 # encoding: utf-8
+
 from antlr4 import *
 from io import StringIO
 import sys
@@ -22,39 +23,35 @@ if sys.version_info[1] > 5:
 else:
 	from typing.io import TextIO
 
-
 def serializedATN():
-    with StringIO() as buf:
-        buf.write("\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\3\25")
-        buf.write("R\4\2\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b")
-        buf.write("\t\b\4\t\t\t\3\2\3\2\3\2\3\3\3\3\5\3\30\n\3\3\4\3\4\3")
-        buf.write("\4\3\4\3\4\3\4\3\4\3\4\5\4\"\n\4\3\5\3\5\3\5\3\5\7\5(")
-        buf.write("\n\5\f\5\16\5+\13\5\3\5\3\5\3\5\3\5\5\5\61\n\5\3\6\3\6")
-        buf.write("\3\6\3\6\3\7\3\7\3\7\3\7\7\7;\n\7\f\7\16\7>\13\7\3\7\3")
-        buf.write("\7\3\7\3\7\5\7D\n\7\3\b\3\b\3\b\3\t\3\t\3\t\5\tL\n\t\3")
-        buf.write("\t\3\t\5\tP\n\t\3\t\2\2\n\2\4\6\b\n\f\16\20\2\2\2Y\2\22")
-        buf.write("\3\2\2\2\4\25\3\2\2\2\6!\3\2\2\2\b\60\3\2\2\2\n\62\3\2")
-        buf.write("\2\2\fC\3\2\2\2\16E\3\2\2\2\20O\3\2\2\2\22\23\5\4\3\2")
-        buf.write("\23\24\7\2\2\3\24\3\3\2\2\2\25\27\5\6\4\2\26\30\5\16\b")
-        buf.write("\2\27\26\3\2\2\2\27\30\3\2\2\2\30\5\3\2\2\2\31\"\7\21")
-        buf.write("\2\2\32\"\7\22\2\2\33\"\5\20\t\2\34\"\7\3\2\2\35\"\7\4")
-        buf.write("\2\2\36\"\7\5\2\2\37\"\5\b\5\2 \"\5\f\7\2!\31\3\2\2\2")
-        buf.write("!\32\3\2\2\2!\33\3\2\2\2!\34\3\2\2\2!\35\3\2\2\2!\36\3")
-        buf.write("\2\2\2!\37\3\2\2\2! \3\2\2\2\"\7\3\2\2\2#$\7\6\2\2$)\5")
-        buf.write("\n\6\2%&\7\7\2\2&(\5\n\6\2\'%\3\2\2\2(+\3\2\2\2)\'\3\2")
-        buf.write("\2\2)*\3\2\2\2*,\3\2\2\2+)\3\2\2\2,-\7\b\2\2-\61\3\2\2")
-        buf.write("\2./\7\6\2\2/\61\7\b\2\2\60#\3\2\2\2\60.\3\2\2\2\61\t")
-        buf.write("\3\2\2\2\62\63\7\21\2\2\63\64\7\t\2\2\64\65\5\4\3\2\65")
-        buf.write("\13\3\2\2\2\66\67\7\n\2\2\67<\5\4\3\289\7\7\2\29;\5\4")
-        buf.write("\3\2:8\3\2\2\2;>\3\2\2\2<:\3\2\2\2<=\3\2\2\2=?\3\2\2\2")
-        buf.write("><\3\2\2\2?@\7\13\2\2@D\3\2\2\2AB\7\n\2\2BD\7\13\2\2C")
-        buf.write("\66\3\2\2\2CA\3\2\2\2D\r\3\2\2\2EF\7\f\2\2FG\7\20\2\2")
-        buf.write("G\17\3\2\2\2HP\7\23\2\2IP\7\24\2\2JL\7\r\2\2KJ\3\2\2\2")
-        buf.write("KL\3\2\2\2LM\3\2\2\2MP\7\16\2\2NP\7\17\2\2OH\3\2\2\2O")
-        buf.write("I\3\2\2\2OK\3\2\2\2ON\3\2\2\2P\21\3\2\2\2\n\27!)\60<C")
-        buf.write("KO")
-        return buf.getvalue()
-
+    return [
+        4,1,19,80,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,
+        6,2,7,7,7,1,0,1,0,1,0,1,1,1,1,3,1,22,8,1,1,2,1,2,1,2,1,2,1,2,1,2,
+        1,2,1,2,3,2,32,8,2,1,3,1,3,1,3,1,3,5,3,38,8,3,10,3,12,3,41,9,3,1,
+        3,1,3,1,3,1,3,3,3,47,8,3,1,4,1,4,1,4,1,4,1,5,1,5,1,5,1,5,5,5,57,
+        8,5,10,5,12,5,60,9,5,1,5,1,5,1,5,1,5,3,5,66,8,5,1,6,1,6,1,6,1,7,
+        1,7,1,7,3,7,74,8,7,1,7,1,7,3,7,78,8,7,1,7,0,0,8,0,2,4,6,8,10,12,
+        14,0,0,87,0,16,1,0,0,0,2,19,1,0,0,0,4,31,1,0,0,0,6,46,1,0,0,0,8,
+        48,1,0,0,0,10,65,1,0,0,0,12,67,1,0,0,0,14,77,1,0,0,0,16,17,3,2,1,
+        0,17,18,5,0,0,1,18,1,1,0,0,0,19,21,3,4,2,0,20,22,3,12,6,0,21,20,
+        1,0,0,0,21,22,1,0,0,0,22,3,1,0,0,0,23,32,5,15,0,0,24,32,5,16,0,0,
+        25,32,3,14,7,0,26,32,5,1,0,0,27,32,5,2,0,0,28,32,5,3,0,0,29,32,3,
+        6,3,0,30,32,3,10,5,0,31,23,1,0,0,0,31,24,1,0,0,0,31,25,1,0,0,0,31,
+        26,1,0,0,0,31,27,1,0,0,0,31,28,1,0,0,0,31,29,1,0,0,0,31,30,1,0,0,
+        0,32,5,1,0,0,0,33,34,5,4,0,0,34,39,3,8,4,0,35,36,5,5,0,0,36,38,3,
+        8,4,0,37,35,1,0,0,0,38,41,1,0,0,0,39,37,1,0,0,0,39,40,1,0,0,0,40,
+        42,1,0,0,0,41,39,1,0,0,0,42,43,5,6,0,0,43,47,1,0,0,0,44,45,5,4,0,
+        0,45,47,5,6,0,0,46,33,1,0,0,0,46,44,1,0,0,0,47,7,1,0,0,0,48,49,5,
+        15,0,0,49,50,5,7,0,0,50,51,3,2,1,0,51,9,1,0,0,0,52,53,5,8,0,0,53,
+        58,3,2,1,0,54,55,5,5,0,0,55,57,3,2,1,0,56,54,1,0,0,0,57,60,1,0,0,
+        0,58,56,1,0,0,0,58,59,1,0,0,0,59,61,1,0,0,0,60,58,1,0,0,0,61,62,
+        5,9,0,0,62,66,1,0,0,0,63,64,5,8,0,0,64,66,5,9,0,0,65,52,1,0,0,0,
+        65,63,1,0,0,0,66,11,1,0,0,0,67,68,5,10,0,0,68,69,5,14,0,0,69,13,
+        1,0,0,0,70,78,5,17,0,0,71,78,5,18,0,0,72,74,5,11,0,0,73,72,1,0,0,
+        0,73,74,1,0,0,0,74,75,1,0,0,0,75,78,5,12,0,0,76,78,5,13,0,0,77,70,
+        1,0,0,0,77,71,1,0,0,0,77,73,1,0,0,0,77,76,1,0,0,0,78,15,1,0,0,0,
+        8,21,31,39,46,58,65,73,77
+    ]
 
 class AgtypeParser ( Parser ):
 
@@ -111,7 +108,7 @@ class AgtypeParser ( Parser ):
 
     def __init__(self, input:TokenStream, output:TextIO = sys.stdout):
         super().__init__(input, output)
-        self.checkVersion("4.9.2")
+        self.checkVersion("4.11.1")
         self._interp = ParserATNSimulator(self, self.atn, self.decisionsToDFA, self.sharedContextCache)
         self._predicates = None
 
@@ -218,7 +215,7 @@ class AgtypeParser ( Parser ):
             self.state = 21
             self._errHandler.sync(self)
             _la = self._input.LA(1)
-            if _la==AgtypeParser.T__9:
+            if _la==10:
                 self.state = 20
                 self.typeAnnotation()
 
@@ -447,49 +444,49 @@ class AgtypeParser ( Parser ):
             self.state = 31
             self._errHandler.sync(self)
             token = self._input.LA(1)
-            if token in [AgtypeParser.STRING]:
+            if token in [15]:
                 localctx = AgtypeParser.StringValueContext(self, localctx)
                 self.enterOuterAlt(localctx, 1)
                 self.state = 23
                 self.match(AgtypeParser.STRING)
                 pass
-            elif token in [AgtypeParser.INTEGER]:
+            elif token in [16]:
                 localctx = AgtypeParser.IntegerValueContext(self, localctx)
                 self.enterOuterAlt(localctx, 2)
                 self.state = 24
                 self.match(AgtypeParser.INTEGER)
                 pass
-            elif token in [AgtypeParser.T__10, AgtypeParser.T__11, AgtypeParser.T__12, AgtypeParser.RegularFloat, AgtypeParser.ExponentFloat]:
+            elif token in [11, 12, 13, 17, 18]:
                 localctx = AgtypeParser.FloatValueContext(self, localctx)
                 self.enterOuterAlt(localctx, 3)
                 self.state = 25
                 self.floatLiteral()
                 pass
-            elif token in [AgtypeParser.T__0]:
+            elif token in [1]:
                 localctx = AgtypeParser.TrueBooleanContext(self, localctx)
                 self.enterOuterAlt(localctx, 4)
                 self.state = 26
                 self.match(AgtypeParser.T__0)
                 pass
-            elif token in [AgtypeParser.T__1]:
+            elif token in [2]:
                 localctx = AgtypeParser.FalseBooleanContext(self, localctx)
                 self.enterOuterAlt(localctx, 5)
                 self.state = 27
                 self.match(AgtypeParser.T__1)
                 pass
-            elif token in [AgtypeParser.T__2]:
+            elif token in [3]:
                 localctx = AgtypeParser.NullValueContext(self, localctx)
                 self.enterOuterAlt(localctx, 6)
                 self.state = 28
                 self.match(AgtypeParser.T__2)
                 pass
-            elif token in [AgtypeParser.T__3]:
+            elif token in [4]:
                 localctx = AgtypeParser.ObjectValueContext(self, localctx)
                 self.enterOuterAlt(localctx, 7)
                 self.state = 29
                 self.obj()
                 pass
-            elif token in [AgtypeParser.T__7]:
+            elif token in [8]:
                 localctx = AgtypeParser.ArrayValueContext(self, localctx)
                 self.enterOuterAlt(localctx, 8)
                 self.state = 30
@@ -559,7 +556,7 @@ class AgtypeParser ( Parser ):
                 self.state = 39
                 self._errHandler.sync(self)
                 _la = self._input.LA(1)
-                while _la==AgtypeParser.T__4:
+                while _la==5:
                     self.state = 35
                     self.match(AgtypeParser.T__4)
                     self.state = 36
@@ -697,7 +694,7 @@ class AgtypeParser ( Parser ):
                 self.state = 58
                 self._errHandler.sync(self)
                 _la = self._input.LA(1)
-                while _la==AgtypeParser.T__4:
+                while _la==5:
                     self.state = 54
                     self.match(AgtypeParser.T__4)
                     self.state = 55
@@ -819,22 +816,22 @@ class AgtypeParser ( Parser ):
             self.state = 77
             self._errHandler.sync(self)
             token = self._input.LA(1)
-            if token in [AgtypeParser.RegularFloat]:
+            if token in [17]:
                 self.enterOuterAlt(localctx, 1)
                 self.state = 70
                 self.match(AgtypeParser.RegularFloat)
                 pass
-            elif token in [AgtypeParser.ExponentFloat]:
+            elif token in [18]:
                 self.enterOuterAlt(localctx, 2)
                 self.state = 71
                 self.match(AgtypeParser.ExponentFloat)
                 pass
-            elif token in [AgtypeParser.T__10, AgtypeParser.T__11]:
+            elif token in [11, 12]:
                 self.enterOuterAlt(localctx, 3)
                 self.state = 73
                 self._errHandler.sync(self)
                 _la = self._input.LA(1)
-                if _la==AgtypeParser.T__10:
+                if _la==11:
                     self.state = 72
                     self.match(AgtypeParser.T__10)
 
@@ -842,7 +839,7 @@ class AgtypeParser ( Parser ):
                 self.state = 75
                 self.match(AgtypeParser.T__11)
                 pass
-            elif token in [AgtypeParser.T__12]:
+            elif token in [13]:
                 self.enterOuterAlt(localctx, 4)
                 self.state = 76
                 self.match(AgtypeParser.T__12)
@@ -859,6 +856,3 @@ class AgtypeParser ( Parser ):
         return localctx
 
 
-
-
-
diff --git a/drivers/python/age/gen/AgtypeVisitor.py b/drivers/python/age/gen/AgtypeVisitor.py
index 2f48c4ba..824eb512 100644
--- a/drivers/python/age/gen/AgtypeVisitor.py
+++ b/drivers/python/age/gen/AgtypeVisitor.py
@@ -12,7 +12,8 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-# Generated from Agtype.g4 by ANTLR 4.9.2
+# Generated from ../Agtype.g4 by ANTLR 4.11.1
+
 from antlr4 import *
 if __name__ is not None and "." in __name__:
     from .AgtypeParser import AgtypeParser
diff --git a/drivers/python/antlr/README.md b/drivers/python/antlr/README.md
index bd817adc..8b070e9a 100644
--- a/drivers/python/antlr/README.md
+++ b/drivers/python/antlr/README.md
@@ -7,7 +7,17 @@ Python driver for Apache AGE, graph extension for PostgreSQL.
 ```
 # prerequisites : 
 #    - java over 8
-#    - download ANTLR4 from https://www.antlr.org/download/antlr-4.9.2-complete.jar
-#    - java -cp antlr-4.9.2-complete.jar org.antlr.v4.Tool  -Dlanguage=Python3 -visitor -o ../age/gen ../../Agtype.g4 
+#    - download ANTLR4 from https://www.antlr.org/download/antlr-4.11.1-complete.jar
+#    - java -cp antlr-4.11.1-complete.jar org.antlr.v4.Tool  -Dlanguage=Python3 -visitor -o ../age/gen ../../Agtype.g4 
 ```
 
+
+#### 2) Remove the *.interp & *.tokens files
+
+#### 3) Proceed to unit testing
+```
+python -m unittest -v test_age_py.py
+```
+```
+python -m unittest -v test_agtypes.py
+`
diff --git a/drivers/python/requirements.txt b/drivers/python/requirements.txt
index 918763cb..3aff61d8 100644
Binary files a/drivers/python/requirements.txt and b/drivers/python/requirements.txt differ
diff --git a/drivers/python/setup.py b/drivers/python/setup.py
index a7979a26..4565dc5d 100644
--- a/drivers/python/setup.py
+++ b/drivers/python/setup.py
@@ -29,7 +29,7 @@ setup(
     author_email     = 'rhizome.ai@gmail.com',
     url              = 'https://github.com/apache/age',
     license          = 'Apache2.0',
-    install_requires = [ 'psycopg2', 'antlr4-python3-runtime==4.9.3'],
+    install_requires = [ 'psycopg2', 'antlr4-python3-runtime==4.11.1'],
     packages         = ['age', 'age.gen'],
     keywords         = ['Graph Database', 'Apache AGE', 'PostgreSQL'],
     python_requires  = '>=3.9',


[age] 18/19: Remove check for scalar agtypes in unwind (#736)

Posted by jg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 68e3b5aedd9ad9fcee19505de9f5844023935e0e
Author: Muhammad Taha Naveed <mn...@seecs.edu.pk>
AuthorDate: Sat Mar 11 05:51:42 2023 +0500

    Remove check for scalar agtypes in unwind (#736)
    
    - Added regression tests for unwind.
---
 age--1.2.0.sql                     |   8 +-
 regress/expected/cypher_unwind.out | 155 +++++++++++++++++++++++++++++++++----
 regress/sql/cypher_unwind.sql      |  92 ++++++++++++++++++++--
 src/backend/parser/cypher_clause.c |   2 +-
 src/backend/utils/adt/agtype.c     |  14 +---
 5 files changed, 232 insertions(+), 39 deletions(-)

diff --git a/age--1.2.0.sql b/age--1.2.0.sql
index e50f1a62..c8d7ecd0 100644
--- a/age--1.2.0.sql
+++ b/age--1.2.0.sql
@@ -4162,10 +4162,10 @@ IMMUTABLE
 PARALLEL SAFE
 AS 'MODULE_PATHNAME';
 
-CREATE FUNCTION ag_catalog.age_unnest(agtype, block_types boolean = false)
-    RETURNS SETOF agtype
-    LANGUAGE c
-    IMMUTABLE
+CREATE FUNCTION ag_catalog.age_unnest(agtype)
+RETURNS SETOF agtype
+LANGUAGE c
+IMMUTABLE
 PARALLEL SAFE
 AS 'MODULE_PATHNAME';
 
diff --git a/regress/expected/cypher_unwind.out b/regress/expected/cypher_unwind.out
index d0a71717..3c454f10 100644
--- a/regress/expected/cypher_unwind.out
+++ b/regress/expected/cypher_unwind.out
@@ -25,25 +25,48 @@ NOTICE:  graph "cypher_unwind" has been created
  
 (1 row)
 
+-- Create nodes and relations
 SELECT * FROM cypher('cypher_unwind', $$
-    UNWIND [1, 2, 3] AS i RETURN i
+    CREATE (n {name: 'node1', a: [1, 2, 3]}),
+           (m {name: 'node2', a: [4, 5, 6]}),
+           (o {name: 'node3', a: [7, 8, 9]}),
+           (n)-[:KNOWS]->(m),
+           (m)-[:KNOWS]->(o)
 $$) as (i agtype);
  i 
 ---
- 1
- 2
- 3
+(0 rows)
+
+SELECT * FROM cypher('cypher_unwind', $$
+    MATCH (n)
+    RETURN n
+$$) as (i agtype);
+                                               i                                               
+-----------------------------------------------------------------------------------------------
+ {"id": 281474976710657, "label": "", "properties": {"a": [1, 2, 3], "name": "node1"}}::vertex
+ {"id": 281474976710658, "label": "", "properties": {"a": [4, 5, 6], "name": "node2"}}::vertex
+ {"id": 281474976710659, "label": "", "properties": {"a": [7, 8, 9], "name": "node3"}}::vertex
 (3 rows)
 
+--
+-- Test UNWIND clause
+--
 SELECT * FROM cypher('cypher_unwind', $$
-    CREATE ({a: [1, 2, 3]}), ({a: [4, 5, 6]})
+    UNWIND [1, 2, 3] AS i
+    RETURN i
 $$) as (i agtype);
  i 
 ---
-(0 rows)
+ 1
+ 2
+ 3
+(3 rows)
 
 SELECT * FROM cypher('cypher_unwind', $$
-    MATCH (n) WITH n.a AS a UNWIND a AS i RETURN *
+    MATCH (n)
+    WITH n.a AS a
+    UNWIND a AS i
+    RETURN *
 $$) as (i agtype, j agtype);
      i     | j 
 -----------+---
@@ -53,7 +76,10 @@ $$) as (i agtype, j agtype);
  [4, 5, 6] | 4
  [4, 5, 6] | 5
  [4, 5, 6] | 6
-(6 rows)
+ [7, 8, 9] | 7
+ [7, 8, 9] | 8
+ [7, 8, 9] | 9
+(9 rows)
 
 SELECT * FROM cypher('cypher_unwind', $$
     WITH [[1, 2], [3, 4], 5] AS nested
@@ -70,17 +96,118 @@ $$) as (i agtype);
  5
 (5 rows)
 
+-- UNWIND vertices
+SELECT * FROM cypher('cypher_unwind', $$
+    MATCH p=(n)-[:KNOWS]->(m)
+    UNWIND nodes(p) as node
+    RETURN node
+$$) as (i agtype);
+                                               i                                               
+-----------------------------------------------------------------------------------------------
+ {"id": 281474976710657, "label": "", "properties": {"a": [1, 2, 3], "name": "node1"}}::vertex
+ {"id": 281474976710658, "label": "", "properties": {"a": [4, 5, 6], "name": "node2"}}::vertex
+ {"id": 281474976710658, "label": "", "properties": {"a": [4, 5, 6], "name": "node2"}}::vertex
+ {"id": 281474976710659, "label": "", "properties": {"a": [7, 8, 9], "name": "node3"}}::vertex
+(4 rows)
+
 SELECT * FROM cypher('cypher_unwind', $$
-    WITH [{id: 0, label:'', properties:{}}::vertex, {id: 1, label:'', properties:{}}::vertex] as n
-    UNWIND n as a
-    SET a.i = 1
-    RETURN a
+    MATCH p=(n)-[:KNOWS]->(m)
+    UNWIND nodes(p) as node
+    RETURN node.name
 $$) as (i agtype);
-ERROR:  UNWIND clause does not support agtype vertex
+    i    
+---------
+ "node1"
+ "node2"
+ "node2"
+ "node3"
+(4 rows)
+
+-- UNWIND edges
+SELECT * FROM cypher('cypher_unwind', $$
+    MATCH p=(n)-[:KNOWS]->(m)
+    UNWIND relationships(p) as relation
+    RETURN relation
+$$) as (i agtype);
+                                                             i                                                             
+---------------------------------------------------------------------------------------------------------------------------
+ {"id": 844424930131969, "label": "KNOWS", "end_id": 281474976710658, "start_id": 281474976710657, "properties": {}}::edge
+ {"id": 844424930131970, "label": "KNOWS", "end_id": 281474976710659, "start_id": 281474976710658, "properties": {}}::edge
+(2 rows)
+
+SELECT * FROM cypher('cypher_unwind', $$
+    MATCH p=(n)-[:KNOWS]->(m)
+    UNWIND relationships(p) as relation
+    RETURN type(relation)
+$$) as (i agtype);
+    i    
+---------
+ "KNOWS"
+ "KNOWS"
+(2 rows)
+
+-- UNWIND paths (vle)
+SELECT * FROM cypher('cypher_unwind', $$
+    MATCH p=({name:'node1'})-[e:KNOWS*]->({name:'node3'})
+    UNWIND [p] as path
+    RETURN path
+$$) as (i agtype);
+                                                                                                                                                                                                                                                                                                     i                                                                                                                                                                                                       [...]
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ [...]
+ [{"id": 281474976710657, "label": "_ag_label_vertex", "properties": {"a": [1, 2, 3], "name": "node1"}}::vertex, {"id": 844424930131969, "label": "KNOWS", "end_id": 281474976710658, "start_id": 281474976710657, "properties": {}}::edge, {"id": 281474976710658, "label": "_ag_label_vertex", "properties": {"a": [4, 5, 6], "name": "node2"}}::vertex, {"id": 844424930131970, "label": "KNOWS", "end_id": 281474976710659, "start_id": 281474976710658, "properties": {}}::edge, {"id": 281474976710659 [...]
+(1 row)
+
+SELECT * FROM cypher('cypher_unwind', $$
+    MATCH p=({name:'node1'})-[e:KNOWS*]->({name:'node3'})
+    UNWIND [p] as path
+    RETURN relationships(path)
+$$) as (i agtype);
+                                                                                                                           i                                                                                                                            
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ [{"id": 844424930131969, "label": "KNOWS", "end_id": 281474976710658, "start_id": 281474976710657, "properties": {}}::edge, {"id": 844424930131970, "label": "KNOWS", "end_id": 281474976710659, "start_id": 281474976710658, "properties": {}}::edge]
+(1 row)
+
+SELECT * FROM cypher('cypher_unwind', $$
+    MATCH p=({name:'node1'})-[e:KNOWS*]->({name:'node3'})
+    UNWIND [p] as path
+    UNWIND relationships(path) as edge
+    RETURN edge
+$$) as (i agtype);
+                                                             i                                                             
+---------------------------------------------------------------------------------------------------------------------------
+ {"id": 844424930131969, "label": "KNOWS", "end_id": 281474976710658, "start_id": 281474976710657, "properties": {}}::edge
+ {"id": 844424930131970, "label": "KNOWS", "end_id": 281474976710659, "start_id": 281474976710658, "properties": {}}::edge
+(2 rows)
+
+-- Unwind with SET clause
+SELECT * FROM cypher('cypher_unwind', $$
+    MATCH p=(n)-[:KNOWS]->(m)
+    UNWIND nodes(p) as node
+    SET node.type = 'vertex'
+$$) as (i agtype);
+ i 
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_unwind', $$
+    MATCH (n)
+    RETURN n
+$$) as (i agtype);
+                                                        i                                                        
+-----------------------------------------------------------------------------------------------------------------
+ {"id": 281474976710657, "label": "", "properties": {"a": [1, 2, 3], "name": "node1", "type": "vertex"}}::vertex
+ {"id": 281474976710658, "label": "", "properties": {"a": [4, 5, 6], "name": "node2", "type": "vertex"}}::vertex
+ {"id": 281474976710659, "label": "", "properties": {"a": [7, 8, 9], "name": "node3", "type": "vertex"}}::vertex
+(3 rows)
+
+--
+-- Clean up
+--
 SELECT drop_graph('cypher_unwind', true);
-NOTICE:  drop cascades to 2 other objects
+NOTICE:  drop cascades to 3 other objects
 DETAIL:  drop cascades to table cypher_unwind._ag_label_vertex
 drop cascades to table cypher_unwind._ag_label_edge
+drop cascades to table cypher_unwind."KNOWS"
 NOTICE:  graph "cypher_unwind" has been dropped
  drop_graph 
 ------------
diff --git a/regress/sql/cypher_unwind.sql b/regress/sql/cypher_unwind.sql
index d1f2d81f..8e8c74bc 100644
--- a/regress/sql/cypher_unwind.sql
+++ b/regress/sql/cypher_unwind.sql
@@ -22,16 +22,35 @@ SET search_path TO ag_catalog;
 
 SELECT create_graph('cypher_unwind');
 
+-- Create nodes and relations
+
 SELECT * FROM cypher('cypher_unwind', $$
-    UNWIND [1, 2, 3] AS i RETURN i
+    CREATE (n {name: 'node1', a: [1, 2, 3]}),
+           (m {name: 'node2', a: [4, 5, 6]}),
+           (o {name: 'node3', a: [7, 8, 9]}),
+           (n)-[:KNOWS]->(m),
+           (m)-[:KNOWS]->(o)
 $$) as (i agtype);
 
 SELECT * FROM cypher('cypher_unwind', $$
-    CREATE ({a: [1, 2, 3]}), ({a: [4, 5, 6]})
+    MATCH (n)
+    RETURN n
+$$) as (i agtype);
+
+--
+-- Test UNWIND clause
+--
+
+SELECT * FROM cypher('cypher_unwind', $$
+    UNWIND [1, 2, 3] AS i
+    RETURN i
 $$) as (i agtype);
 
 SELECT * FROM cypher('cypher_unwind', $$
-    MATCH (n) WITH n.a AS a UNWIND a AS i RETURN *
+    MATCH (n)
+    WITH n.a AS a
+    UNWIND a AS i
+    RETURN *
 $$) as (i agtype, j agtype);
 
 SELECT * FROM cypher('cypher_unwind', $$
@@ -41,11 +60,70 @@ SELECT * FROM cypher('cypher_unwind', $$
     RETURN y
 $$) as (i agtype);
 
+-- UNWIND vertices
+
+SELECT * FROM cypher('cypher_unwind', $$
+    MATCH p=(n)-[:KNOWS]->(m)
+    UNWIND nodes(p) as node
+    RETURN node
+$$) as (i agtype);
+
+SELECT * FROM cypher('cypher_unwind', $$
+    MATCH p=(n)-[:KNOWS]->(m)
+    UNWIND nodes(p) as node
+    RETURN node.name
+$$) as (i agtype);
+
+-- UNWIND edges
+
+SELECT * FROM cypher('cypher_unwind', $$
+    MATCH p=(n)-[:KNOWS]->(m)
+    UNWIND relationships(p) as relation
+    RETURN relation
+$$) as (i agtype);
+
+SELECT * FROM cypher('cypher_unwind', $$
+    MATCH p=(n)-[:KNOWS]->(m)
+    UNWIND relationships(p) as relation
+    RETURN type(relation)
+$$) as (i agtype);
+
+-- UNWIND paths (vle)
+
+SELECT * FROM cypher('cypher_unwind', $$
+    MATCH p=({name:'node1'})-[e:KNOWS*]->({name:'node3'})
+    UNWIND [p] as path
+    RETURN path
+$$) as (i agtype);
+
+SELECT * FROM cypher('cypher_unwind', $$
+    MATCH p=({name:'node1'})-[e:KNOWS*]->({name:'node3'})
+    UNWIND [p] as path
+    RETURN relationships(path)
+$$) as (i agtype);
+
 SELECT * FROM cypher('cypher_unwind', $$
-    WITH [{id: 0, label:'', properties:{}}::vertex, {id: 1, label:'', properties:{}}::vertex] as n
-    UNWIND n as a
-    SET a.i = 1
-    RETURN a
+    MATCH p=({name:'node1'})-[e:KNOWS*]->({name:'node3'})
+    UNWIND [p] as path
+    UNWIND relationships(path) as edge
+    RETURN edge
 $$) as (i agtype);
 
+-- Unwind with SET clause
+
+SELECT * FROM cypher('cypher_unwind', $$
+    MATCH p=(n)-[:KNOWS]->(m)
+    UNWIND nodes(p) as node
+    SET node.type = 'vertex'
+$$) as (i agtype);
+
+SELECT * FROM cypher('cypher_unwind', $$
+    MATCH (n)
+    RETURN n
+$$) as (i agtype);
+
+--
+-- Clean up
+--
+
 SELECT drop_graph('cypher_unwind', true);
\ No newline at end of file
diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c
index 294ebb06..526a23fb 100644
--- a/src/backend/parser/cypher_clause.c
+++ b/src/backend/parser/cypher_clause.c
@@ -1331,7 +1331,7 @@ static Query *transform_cypher_unwind(cypher_parsestate *cpstate,
     old_expr_kind = pstate->p_expr_kind;
     pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
     funcexpr = ParseFuncOrColumn(pstate, unwind->funcname,
-                                 list_make2(expr, makeBoolConst(true, false)),
+                                 list_make1(expr),
                                  pstate->p_last_srf, unwind, false,
                                  target_syntax_loc);
 
diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c
index ed4bf4d4..cba3316b 100644
--- a/src/backend/utils/adt/agtype.c
+++ b/src/backend/utils/adt/agtype.c
@@ -10031,14 +10031,11 @@ Datum age_range(PG_FUNCTION_ARGS)
 PG_FUNCTION_INFO_V1(age_unnest);
 /*
  * Function to convert the Array type of Agtype into each row. It is used for
- * Cypher `UNWIND` clause, but considering the situation in which the user can
- * directly use this function in vanilla PGSQL, put a second parameter related
- * to this.
+ * Cypher `UNWIND` clause.
  */
 Datum age_unnest(PG_FUNCTION_ARGS)
 {
     agtype *agtype_arg = AG_GET_ARG_AGTYPE_P(0);
-    bool block_types = PG_GETARG_BOOL(1);
     ReturnSetInfo *rsi;
     Tuplestorestate *tuple_store;
     TupleDesc tupdesc;
@@ -10090,15 +10087,6 @@ Datum age_unnest(PG_FUNCTION_ARGS)
             bool nulls[1] = {false};
             agtype *val = agtype_value_to_agtype(&v);
 
-            if (block_types && (
-                    v.type == AGTV_VERTEX || v.type == AGTV_EDGE || v.type == AGTV_PATH))
-            {
-                ereport(ERROR,
-                        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                                errmsg("UNWIND clause does not support agtype %s",
-                                       agtype_value_type_to_string(v.type))));
-            }
-
             /* use the tmp context so we can clean up after each tuple is done */
             old_cxt = MemoryContextSwitchTo(tmp_cxt);
 


[age] 12/19: Fix cypher function input argument checks (issue #718)

Posted by jg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 8d1febe7819fd128acc4afcf2c299097df16ae33
Author: John Gemignani <jr...@gmail.com>
AuthorDate: Wed Mar 1 10:43:30 2023 -0800

    Fix cypher function input argument checks (issue #718)
    
    Fixed issue #718 which was due to a lack of input checks for an
    edge case and added regression tests.
    
    Addition of the driver parameter patch caused 2 additional edge
    cases that needed to be checked -
    
        SELECT * FROM cypher() AS (result agtype);
        SELECT * FROM cypher(NULL) AS (result agtype);
---
 regress/expected/analyze.out        | 5 +++++
 regress/sql/analyze.sql             | 4 ++++
 src/backend/parser/cypher_analyze.c | 9 +++++++++
 3 files changed, 18 insertions(+)

diff --git a/regress/expected/analyze.out b/regress/expected/analyze.out
index 5b248385..676dab9d 100644
--- a/regress/expected/analyze.out
+++ b/regress/expected/analyze.out
@@ -119,6 +119,11 @@ SELECT * FROM cypher(NULL, NULL) AS (result agtype);
 ERROR:  unexpected character at or near "$"
 LINE 1: SELECT * FROM cypher(NULL, NULL) AS (result agtype);
         ^
+-- should return errors
+SELECT * FROM cypher() AS (result agtype);
+ERROR:  cypher function requires a minimum of 2 arguments
+SELECT * FROM cypher(NULL) AS (result agtype);
+ERROR:  cypher function requires a minimum of 2 arguments
 -- drop graphs
 SELECT * FROM drop_graph('analyze', true);
 NOTICE:  drop cascades to 2 other objects
diff --git a/regress/sql/analyze.sql b/regress/sql/analyze.sql
index 881ef3ff..1da70b23 100644
--- a/regress/sql/analyze.sql
+++ b/regress/sql/analyze.sql
@@ -52,6 +52,10 @@ SELECT * FROM cypher(NULL, NULL) AS (result agtype);
 SELECT * FROM age_prepare_cypher('analyze', '$$ $$');
 SELECT * FROM cypher(NULL, NULL) AS (result agtype);
 
+-- should return errors
+SELECT * FROM cypher() AS (result agtype);
+SELECT * FROM cypher(NULL) AS (result agtype);
+
 -- drop graphs
 SELECT * FROM drop_graph('analyze', true);
 
diff --git a/src/backend/parser/cypher_analyze.c b/src/backend/parser/cypher_analyze.c
index b99ff0a9..425b1167 100644
--- a/src/backend/parser/cypher_analyze.c
+++ b/src/backend/parser/cypher_analyze.c
@@ -331,6 +331,15 @@ static void convert_cypher_to_subquery(RangeTblEntry *rte, ParseState *pstate)
                  parser_errposition(pstate, exprLocation((Node *)funcexpr))));
     }
 
+    /* verify that we have 2 input parameters as it is possible to get 1 or 0 */
+    if (funcexpr->args == NULL || list_length(funcexpr->args) < 2)
+    {
+        ereport(ERROR,
+                (errcode(ERRCODE_SYNTAX_ERROR),
+                 errmsg("cypher function requires a minimum of 2 arguments"),
+                 parser_errposition(pstate, -1)));
+    }
+
     /* get our first 2 arguments */
     arg1 = linitial(funcexpr->args);
     arg2 = lsecond(funcexpr->args);


[age] 08/19: Modify docker url for JDBC driver tests (#716)

Posted by jg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit c3331ed7d1575e708711c5b6c8850ff092ae2276
Author: Muhammad Taha Naveed <85...@users.noreply.github.com>
AuthorDate: Fri Feb 24 23:32:09 2023 +0500

    Modify docker url for JDBC driver tests (#716)
    
    - Update JDBC driver tests to pull image from apache/age dockerhub repo.
---
 .../jdbc/lib/src/test/java/org/apache/age/jdbc/BaseDockerizedTest.java  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/jdbc/lib/src/test/java/org/apache/age/jdbc/BaseDockerizedTest.java b/drivers/jdbc/lib/src/test/java/org/apache/age/jdbc/BaseDockerizedTest.java
index 8024f1a8..8e6aea46 100644
--- a/drivers/jdbc/lib/src/test/java/org/apache/age/jdbc/BaseDockerizedTest.java
+++ b/drivers/jdbc/lib/src/test/java/org/apache/age/jdbc/BaseDockerizedTest.java
@@ -51,7 +51,7 @@ public class BaseDockerizedTest {
     public void beforeAll() throws Exception {
         String CORRECT_DB_PASSWORDS = "postgres";
         agensGraphContainer = new GenericContainer<>(DockerImageName
-            .parse("sorrell/agensgraph-extension-alpine:latest"))
+            .parse("apache/age:latest"))
             .withEnv("POSTGRES_PASSWORD", CORRECT_DB_PASSWORDS)
             .withExposedPorts(5432);
         agensGraphContainer.start();


[age] 01/19: Updated Readme for drivers folder (#642)

Posted by jg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 58a46ed853173531deee08839c1e84424bcbea79
Author: fatima <63...@users.noreply.github.com>
AuthorDate: Thu Feb 16 00:16:22 2023 +0000

    Updated Readme for drivers folder (#642)
    
    * Updated Readme for drivers folder and added important links related to
    Apache-AGE project which were included in other readme files.
    
    * Updated NodeJS driver readme and added important links of Apache-AGE
    project as included in other readme files.
---
 drivers/README           | 5 +++++
 drivers/nodejs/README.md | 4 ++++
 2 files changed, 9 insertions(+)

diff --git a/drivers/README b/drivers/README
index 2026129f..19f34d8b 100644
--- a/drivers/README
+++ b/drivers/README
@@ -16,3 +16,8 @@
 # under the License.
 
 This folder contains drivers for specific languages
+
+### For more information about [Apache AGE](https://age.apache.org/)
+* Apache Age : https://age.apache.org/
+* Github : https://github.com/apache/age
+* Document : https://age.apache.org/age-manual/master/index.html
diff --git a/drivers/nodejs/README.md b/drivers/nodejs/README.md
index f2d4228c..c1563220 100644
--- a/drivers/nodejs/README.md
+++ b/drivers/nodejs/README.md
@@ -46,3 +46,7 @@ const results: QueryResultRow = await client?.query<QueryResultRow>(`
     $$) as (a agtype);
 `)!
 ```
+### For more information about [Apache AGE](https://age.apache.org/)
+* Apache Age : https://age.apache.org/
+* Github : https://github.com/apache/age
+* Document : https://age.apache.org/age-manual/master/index.html


[age] 06/19: Barbell graph generation (#648)

Posted by jg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 0c793704e85b82028710d20c5903fe21583a0c3e
Author: Marco Aurélio Silva de Souza Júnior <57...@users.noreply.github.com>
AuthorDate: Fri Feb 17 15:45:05 2023 -0300

    Barbell graph generation (#648)
    
    * Added function to create a Barbell Graph
    
    * Created auxiliary function to get_next_val_internal;
    * Currently creates a bridge of no vertexes between graphs,
    will be implemented next.
    
    * Added signature to age_create_barbell_graph
    
    * Moved bracket to next line to macth code standards
    
    * Fixed patterns to match code standards
    
    * moved PG_FUNCTION_INFO_V1(age_create_barbell_graph) next to
      age_create_barbell_graph function;
    * added brackets to if/else conditions if just one line;
    * moved get_nextval_internal auxiliary function to the beggining of
      the file.
---
 age--1.2.0.sql                       |  13 +++
 src/backend/utils/graph_generation.c | 156 ++++++++++++++++++++++++++++++++++-
 2 files changed, 165 insertions(+), 4 deletions(-)

diff --git a/age--1.2.0.sql b/age--1.2.0.sql
index 3075824c..e34b3c97 100644
--- a/age--1.2.0.sql
+++ b/age--1.2.0.sql
@@ -4182,6 +4182,19 @@ CALLED ON NULL INPUT
 PARALLEL SAFE
 AS 'MODULE_PATHNAME';
 
+CREATE FUNCTION ag_catalog.age_create_barbell_graph(graph_name name, 
+                                                graph_size int, 
+                                                bridge_size int,
+                                                node_label name = NULL,
+                                                node_properties agtype = NULL,
+                                                edge_label name = NULL,
+                                                edge_properties agtype = NULL)
+RETURNS void
+LANGUAGE c
+CALLED ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
 CREATE FUNCTION ag_catalog.age_prepare_cypher(cstring, cstring)
 RETURNS boolean
 LANGUAGE c
diff --git a/src/backend/utils/graph_generation.c b/src/backend/utils/graph_generation.c
index 49da5f82..80ef85f2 100644
--- a/src/backend/utils/graph_generation.c
+++ b/src/backend/utils/graph_generation.c
@@ -41,14 +41,35 @@
 
 #include "catalog/ag_graph.h"
 #include "catalog/ag_label.h"
+#include "commands/graph_commands.h"
 #include "commands/label_commands.h"
 #include "utils/graphid.h"
-#include "commands/graph_commands.h"
 #include "utils/load/age_load.h"
 #include "utils/load/ag_load_edges.h"
 #include "utils/load/ag_load_labels.h"
 
 
+int64 get_nextval_internal(graph_cache_data* graph_cache, 
+                           label_cache_data* label_cache);
+/*
+ * Auxiliary function to get the next internal value in the graph,
+ * so a new object (node or edge) graph id can be composed.
+ */
+
+int64 get_nextval_internal(graph_cache_data* graph_cache, 
+                           label_cache_data* label_cache) 
+{
+    Oid obj_seq_id;
+    char* label_seq_name_str;
+
+    label_seq_name_str = NameStr(label_cache->seq_name);
+    obj_seq_id = get_relname_relid(label_seq_name_str, 
+                                   graph_cache->namespace);
+    
+    return nextval_internal(obj_seq_id, true);
+}
+
+
 PG_FUNCTION_INFO_V1(create_complete_graph);
 
 /*
@@ -125,7 +146,6 @@ Datum create_complete_graph(PG_FUNCTION_ARGS)
     if (!graph_exists(graph_name_str))
     {
         DirectFunctionCall1(create_graph, CStringGetDatum(graph_name));
-
     }
 
     graph_id = get_graph_oid(graph_name_str);
@@ -194,10 +214,138 @@ Datum create_complete_graph(PG_FUNCTION_ARGS)
             insert_edge_simple(graph_id, edge_name_str,
                             object_graph_id, start_vertex_graph_id,
                             end_vertex_graph_id, props);
-            
         }
     }
-
     PG_RETURN_VOID();
 }
 
+
+PG_FUNCTION_INFO_V1(age_create_barbell_graph);
+
+/* 
+ * The barbell graph is two complete graphs connected by a bridge path
+ * Syntax:
+ * ag_catalog.age_create_barbell_graph(graph_name Name,
+ *                                     m int,
+ *                                     n int, 
+ *                                     vertex_label_name Name DEFAULT = NULL,
+ *                                     vertex_properties agtype DEFAULT = NULL,
+ *                                     edge_label_name Name DEAULT = NULL,
+ *                                     edge_properties agtype DEFAULT = NULL)
+ * Input:
+ * 
+ * graph_name - Name of the graph to be created.
+ * m - number of vertices in one complete graph.
+ * n - number of vertices in the bridge path.
+ * vertex_label_name - Name of the label to assign each vertex to.
+ * vertex_properties - Property values to assign each vertex. Default is NULL
+ * edge_label_name - Name of the label to assign each edge to.
+ * edge_properties - Property values to assign each edge. Default is NULL
+ * 
+ * https://en.wikipedia.org/wiki/Barbell_graph
+ */
+
+Datum age_create_barbell_graph(PG_FUNCTION_ARGS) 
+{
+    FunctionCallInfo arguments;
+    Oid graph_oid;
+    Name graph_name;
+    char* graph_name_str;
+
+    int64 start_node_index, end_node_index, nextval;
+
+    Name node_label_name = NULL;
+    int32 node_label_id;
+    char* node_label_str;
+
+    Name edge_label_name;
+    int32 edge_label_id;
+    char* edge_label_str;
+    
+    graphid object_graph_id;
+    graphid start_node_graph_id;
+    graphid end_node_graph_id;
+
+    graph_cache_data* graph_cache;
+    label_cache_data* edge_cache;
+
+    agtype* properties = NULL;
+
+    arguments = fcinfo;
+
+    // create two separate complete graphs
+    DirectFunctionCall4(create_complete_graph, arguments->arg[0], 
+                                               arguments->arg[1],
+                                               arguments->arg[5], 
+                                               arguments->arg[3]);
+    DirectFunctionCall4(create_complete_graph, arguments->arg[0], 
+                                               arguments->arg[1],
+                                               arguments->arg[5], 
+                                               arguments->arg[3]);
+
+    // Handling remaining arguments 
+    /*
+     * graph_name: doesn't need to validate, since the create_complete_graph
+     * function already does that. 
+    */ 
+    graph_name = PG_GETARG_NAME(0);
+    graph_name_str = NameStr(*graph_name);
+    graph_oid = get_graph_oid(graph_name_str);
+
+    /*
+     * int64 bridge_size: currently only stays at zero.
+     * to do: implement bridge with variable number of nodes.
+    */ 
+    if (PG_ARGISNULL(2) || PG_GETARG_INT32(2) < 0 )
+    {
+        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                errmsg("Bridge size must not be NULL or lower than 0")));
+    }
+
+    // node label: if null, gets default label, which is "_ag_label_vertex"
+    if (PG_ARGISNULL(3)) 
+    {
+        namestrcpy(node_label_name, AG_DEFAULT_LABEL_VERTEX);
+    }
+    else 
+    {
+        node_label_name = PG_GETARG_NAME(3);
+    }
+    node_label_str = NameStr(*node_label_name);
+    node_label_id = get_label_id(node_label_str, graph_oid);
+
+    /* 
+     * edge_label: doesn't need to validate, since the create_complete_graph
+     * function already does that. 
+     */
+    edge_label_name = PG_GETARG_NAME(5);
+    edge_label_str = NameStr(*edge_label_name);
+    edge_label_id = get_label_id(edge_label_str, graph_oid);
+
+    /*
+     * Fetching caches to get next values for graph id's, and access nodes
+     * to be connected with edges.
+     */ 
+    graph_cache = search_graph_name_cache(graph_name_str);
+    edge_cache = search_label_name_graph_cache(edge_label_str,graph_oid);
+
+    // connect a node from each graph
+    start_node_index = 1; // first created node, from the first complete graph
+    end_node_index = arguments->arg[1]*2; // last created node, second graph
+
+    // next index to be assigned to a node or edge
+    nextval = get_nextval_internal(graph_cache, edge_cache);
+
+    // build the graph id's of the edge to be created
+    object_graph_id = make_graphid(edge_label_id, nextval);
+    start_node_graph_id = make_graphid(node_label_id, start_node_index);
+    end_node_graph_id = make_graphid(node_label_id, end_node_index);
+    properties = create_empty_agtype();
+
+    // connect two nodes
+    insert_edge_simple(graph_oid, edge_label_str,
+                       object_graph_id, start_node_graph_id,
+                       end_node_graph_id, properties);
+    
+    PG_RETURN_VOID();
+}


[age] 11/19: Barbell regress tests (#708)

Posted by jg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 1b226efcdabe42a71c86c0484b8ae46059503875
Author: Marco Aurélio Silva de Souza Júnior <57...@users.noreply.github.com>
AuthorDate: Tue Feb 28 21:32:08 2023 -0300

    Barbell regress tests (#708)
    
    * Fixed bug where the system was crashing
    when graph_name was passed as NULL.
    
    * added checks for possible NULL arguments before
    calling create_complete_graph.
    
    * Added regress tests for Barbell graph generation
---
 regress/expected/graph_generation.out | 111 ++++++++++++++++++++++++++++++++++
 regress/sql/graph_generation.sql      |  27 +++++++++
 src/backend/utils/graph_generation.c  |  57 ++++++++++-------
 3 files changed, 174 insertions(+), 21 deletions(-)

diff --git a/regress/expected/graph_generation.out b/regress/expected/graph_generation.out
index 118ab6e9..b9ed65b1 100644
--- a/regress/expected/graph_generation.out
+++ b/regress/expected/graph_generation.out
@@ -109,3 +109,114 @@ NOTICE:  graph "gp2" has been dropped
  
 (1 row)
 
+-- Tests for barbell graph generation
+SELECT * FROM age_create_barbell_graph('gp1',5,0,'vertices',NULL,'edges',NULL);
+NOTICE:  graph "gp1" has been created
+NOTICE:  VLabel "vertices" has been created
+NOTICE:  ELabel "edges" has been created
+ age_create_barbell_graph 
+--------------------------
+ 
+(1 row)
+
+SELECT COUNT(*) FROM gp1."edges";
+ count 
+-------
+    21
+(1 row)
+
+SELECT COUNT(*) FROM gp1."vertices";
+ count 
+-------
+    10
+(1 row)
+
+SELECT * FROM cypher('gp1', $$MATCH (a)-[e]->(b) RETURN e$$) as (n agtype);
+                                                             n                                                              
+----------------------------------------------------------------------------------------------------------------------------
+ {"id": 1125899906842625, "label": "edges", "end_id": 844424930131970, "start_id": 844424930131969, "properties": {}}::edge
+ {"id": 1125899906842629, "label": "edges", "end_id": 844424930131971, "start_id": 844424930131970, "properties": {}}::edge
+ {"id": 1125899906842626, "label": "edges", "end_id": 844424930131971, "start_id": 844424930131969, "properties": {}}::edge
+ {"id": 1125899906842627, "label": "edges", "end_id": 844424930131972, "start_id": 844424930131969, "properties": {}}::edge
+ {"id": 1125899906842632, "label": "edges", "end_id": 844424930131972, "start_id": 844424930131971, "properties": {}}::edge
+ {"id": 1125899906842630, "label": "edges", "end_id": 844424930131972, "start_id": 844424930131970, "properties": {}}::edge
+ {"id": 1125899906842634, "label": "edges", "end_id": 844424930131973, "start_id": 844424930131972, "properties": {}}::edge
+ {"id": 1125899906842628, "label": "edges", "end_id": 844424930131973, "start_id": 844424930131969, "properties": {}}::edge
+ {"id": 1125899906842631, "label": "edges", "end_id": 844424930131973, "start_id": 844424930131970, "properties": {}}::edge
+ {"id": 1125899906842633, "label": "edges", "end_id": 844424930131973, "start_id": 844424930131971, "properties": {}}::edge
+ {"id": 1125899906842635, "label": "edges", "end_id": 844424930131975, "start_id": 844424930131974, "properties": {}}::edge
+ {"id": 1125899906842639, "label": "edges", "end_id": 844424930131976, "start_id": 844424930131975, "properties": {}}::edge
+ {"id": 1125899906842636, "label": "edges", "end_id": 844424930131976, "start_id": 844424930131974, "properties": {}}::edge
+ {"id": 1125899906842637, "label": "edges", "end_id": 844424930131977, "start_id": 844424930131974, "properties": {}}::edge
+ {"id": 1125899906842642, "label": "edges", "end_id": 844424930131977, "start_id": 844424930131976, "properties": {}}::edge
+ {"id": 1125899906842640, "label": "edges", "end_id": 844424930131977, "start_id": 844424930131975, "properties": {}}::edge
+ {"id": 1125899906842644, "label": "edges", "end_id": 844424930131978, "start_id": 844424930131977, "properties": {}}::edge
+ {"id": 1125899906842638, "label": "edges", "end_id": 844424930131978, "start_id": 844424930131974, "properties": {}}::edge
+ {"id": 1125899906842641, "label": "edges", "end_id": 844424930131978, "start_id": 844424930131975, "properties": {}}::edge
+ {"id": 1125899906842643, "label": "edges", "end_id": 844424930131978, "start_id": 844424930131976, "properties": {}}::edge
+ {"id": 1125899906842645, "label": "edges", "end_id": 844424930131978, "start_id": 844424930131969, "properties": {}}::edge
+(21 rows)
+
+SELECT * FROM age_create_barbell_graph('gp1',5,0,'vertices',NULL,'edges',NULL);
+ age_create_barbell_graph 
+--------------------------
+ 
+(1 row)
+
+SELECT COUNT(*) FROM gp1."edges";
+ count 
+-------
+    42
+(1 row)
+
+SELECT COUNT(*) FROM gp1."vertices";
+ count 
+-------
+    20
+(1 row)
+
+SELECT * FROM age_create_barbell_graph('gp2',5,10,'vertices',NULL,'edges',NULL);
+NOTICE:  graph "gp2" has been created
+NOTICE:  VLabel "vertices" has been created
+NOTICE:  ELabel "edges" has been created
+ age_create_barbell_graph 
+--------------------------
+ 
+(1 row)
+
+-- SHOULD FAIL
+SELECT * FROM age_create_barbell_graph(NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+ERROR:  Graph name cannot be NULL
+SELECT * FROM age_create_barbell_graph('gp2',NULL,0,'vertices',NULL,'edges',NULL); 
+ERROR:  Graph size cannot be NULL or lower than 3
+SELECT * FROM age_create_barbell_graph('gp3',5,NULL,'vertices',NULL,'edges',NULL);
+ERROR:  Bridge size cannot be NULL or lower than 0
+SELECT * FROM age_create_barbell_graph('gp4',NULL,0,'vertices',NULL,'edges',NULL);
+ERROR:  Graph size cannot be NULL or lower than 3
+SELECT * FROM age_create_barbell_graph('gp5',5,0,'vertices',NULL,NULL,NULL);
+ERROR:  edge label can not be NULL
+-- DROPPING GRAPHS
+SELECT drop_graph('gp1', true);
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table gp1._ag_label_vertex
+drop cascades to table gp1._ag_label_edge
+drop cascades to table gp1.vertices
+drop cascades to table gp1.edges
+NOTICE:  graph "gp1" has been dropped
+ drop_graph 
+------------
+ 
+(1 row)
+
+SELECT drop_graph('gp2', true);
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table gp2._ag_label_vertex
+drop cascades to table gp2._ag_label_edge
+drop cascades to table gp2.vertices
+drop cascades to table gp2.edges
+NOTICE:  graph "gp2" has been dropped
+ drop_graph 
+------------
+ 
+(1 row)
+
diff --git a/regress/sql/graph_generation.sql b/regress/sql/graph_generation.sql
index 1ad90f5a..32fa56dd 100644
--- a/regress/sql/graph_generation.sql
+++ b/regress/sql/graph_generation.sql
@@ -45,3 +45,30 @@ SELECT * FROM create_complete_graph(NULL,NULL,NULL);
 SELECT drop_graph('gp1', true);
 SELECT drop_graph('gp2', true);
 
+
+-- Tests for barbell graph generation
+SELECT * FROM age_create_barbell_graph('gp1',5,0,'vertices',NULL,'edges',NULL);
+
+SELECT COUNT(*) FROM gp1."edges";
+SELECT COUNT(*) FROM gp1."vertices";
+
+SELECT * FROM cypher('gp1', $$MATCH (a)-[e]->(b) RETURN e$$) as (n agtype);
+
+SELECT * FROM age_create_barbell_graph('gp1',5,0,'vertices',NULL,'edges',NULL);
+
+SELECT COUNT(*) FROM gp1."edges";
+SELECT COUNT(*) FROM gp1."vertices";
+
+SELECT * FROM age_create_barbell_graph('gp2',5,10,'vertices',NULL,'edges',NULL);
+
+-- SHOULD FAIL
+SELECT * FROM age_create_barbell_graph(NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+SELECT * FROM age_create_barbell_graph('gp2',NULL,0,'vertices',NULL,'edges',NULL); 
+SELECT * FROM age_create_barbell_graph('gp3',5,NULL,'vertices',NULL,'edges',NULL);
+SELECT * FROM age_create_barbell_graph('gp4',NULL,0,'vertices',NULL,'edges',NULL);
+SELECT * FROM age_create_barbell_graph('gp5',5,0,'vertices',NULL,NULL,NULL);
+
+-- DROPPING GRAPHS
+SELECT drop_graph('gp1', true);
+SELECT drop_graph('gp2', true);
+
diff --git a/src/backend/utils/graph_generation.c b/src/backend/utils/graph_generation.c
index 80ef85f2..b40ef07e 100644
--- a/src/backend/utils/graph_generation.c
+++ b/src/backend/utils/graph_generation.c
@@ -273,25 +273,23 @@ Datum age_create_barbell_graph(PG_FUNCTION_ARGS)
 
     arguments = fcinfo;
 
-    // create two separate complete graphs
-    DirectFunctionCall4(create_complete_graph, arguments->arg[0], 
-                                               arguments->arg[1],
-                                               arguments->arg[5], 
-                                               arguments->arg[3]);
-    DirectFunctionCall4(create_complete_graph, arguments->arg[0], 
-                                               arguments->arg[1],
-                                               arguments->arg[5], 
-                                               arguments->arg[3]);
-
-    // Handling remaining arguments 
-    /*
-     * graph_name: doesn't need to validate, since the create_complete_graph
-     * function already does that. 
-    */ 
+    // Checking for possible NULL arguments 
+    // Name graph_name
+    if (PG_ARGISNULL(0))
+    {
+        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                        errmsg("Graph name cannot be NULL")));
+    }
     graph_name = PG_GETARG_NAME(0);
     graph_name_str = NameStr(*graph_name);
-    graph_oid = get_graph_oid(graph_name_str);
 
+    // int graph size (number of nodes in each complete graph)
+    if (PG_ARGISNULL(1) && PG_GETARG_INT32(1) < 3)
+    {
+        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                errmsg("Graph size cannot be NULL or lower than 3")));
+    }
+    
     /*
      * int64 bridge_size: currently only stays at zero.
      * to do: implement bridge with variable number of nodes.
@@ -299,7 +297,7 @@ Datum age_create_barbell_graph(PG_FUNCTION_ARGS)
     if (PG_ARGISNULL(2) || PG_GETARG_INT32(2) < 0 )
     {
         ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                errmsg("Bridge size must not be NULL or lower than 0")));
+                errmsg("Bridge size cannot be NULL or lower than 0")));
     }
 
     // node label: if null, gets default label, which is "_ag_label_vertex"
@@ -312,14 +310,31 @@ Datum age_create_barbell_graph(PG_FUNCTION_ARGS)
         node_label_name = PG_GETARG_NAME(3);
     }
     node_label_str = NameStr(*node_label_name);
-    node_label_id = get_label_id(node_label_str, graph_oid);
 
     /* 
-     * edge_label: doesn't need to validate, since the create_complete_graph
-     * function already does that. 
-     */
+    * Name edge_label 
+    */
+    if (PG_ARGISNULL(5))
+    {
+        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                errmsg("edge label can not be NULL")));
+    }
     edge_label_name = PG_GETARG_NAME(5);
     edge_label_str = NameStr(*edge_label_name);
+
+
+    // create two separate complete graphs
+    DirectFunctionCall4(create_complete_graph, arguments->arg[0], 
+                                               arguments->arg[1],
+                                               arguments->arg[5], 
+                                               arguments->arg[3]);
+    DirectFunctionCall4(create_complete_graph, arguments->arg[0], 
+                                               arguments->arg[1],
+                                               arguments->arg[5], 
+                                               arguments->arg[3]);
+
+    graph_oid = get_graph_oid(graph_name_str);
+    node_label_id = get_label_id(node_label_str, graph_oid);
     edge_label_id = get_label_id(edge_label_str, graph_oid);
 
     /*


[age] 13/19: Fix Issue #693 - server termination from return exists(path) (#721)

Posted by jg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 4c8ee36ae36801a8035de28239fd87f9f688111d
Author: Muhammad Taha Naveed <mn...@seecs.edu.pk>
AuthorDate: Fri Mar 3 00:16:18 2023 +0500

    Fix Issue #693 - server termination from return exists(path) (#721)
    
    - Updated assert to pass EXPR_KIND_SELECT_TARGET.
    - Added regression tests.
---
 regress/expected/cypher_match.out  | 96 ++++++++++++++++++++++++++++++++++++++
 regress/sql/cypher_match.sql       | 23 +++++++++
 src/backend/parser/cypher_clause.c |  1 +
 3 files changed, 120 insertions(+)

diff --git a/regress/expected/cypher_match.out b/regress/expected/cypher_match.out
index 9ec4112d..8f325cbe 100644
--- a/regress/expected/cypher_match.out
+++ b/regress/expected/cypher_match.out
@@ -720,6 +720,57 @@ AS (u agtype, e agtype, v agtype);
  {"id": 2814749767106561, "label": "loop", "properties": {"id": "initial"}}::vertex | {"id": 3096224743817217, "label": "self", "end_id": 2814749767106561, "start_id": 2814749767106561, "properties": {}}::edge | {"id": 2814749767106561, "label": "loop", "properties": {"id": "initial"}}::vertex
 (1 row)
 
+-- Return exists(pattern)
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (u) RETURN EXISTS((u)-[]->()) $$)
+AS (exists agtype);
+ exists 
+--------
+ false
+ false
+ false
+ false
+ false
+ true
+ true
+ false
+ false
+ true
+ false
+ true
+ false
+ true
+ true
+(15 rows)
+
+SELECT * FROM cypher('cypher_match',
+ $$MATCH p=(u)-[e]->(v) RETURN EXISTS((p)) $$)
+AS (exists agtype);
+ exists 
+--------
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+(7 rows)
+
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (u)-[e]->(v) RETURN EXISTS((u)-[e]->(v)-[e]->(u))$$)
+AS (exists agtype);
+ exists 
+--------
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+(7 rows)
+
 -- These should error
 -- Bad pattern
 SELECT * FROM cypher('cypher_match',
@@ -809,6 +860,51 @@ AS (u agtype);
  {"id": 2814749767106561, "label": "loop", "properties": {"id": "initial"}}::vertex
 (1 row)
 
+-- Return exists(property)
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (u) RETURN EXISTS(u.id), properties(u) $$)
+AS (exists agtype, properties agtype);
+ exists |                                                 properties                                                  
+--------+-------------------------------------------------------------------------------------------------------------
+ false  | {"int_key": 1, "map_key": {"key": "value"}, "list_key": [1, 2, 3], "float_key": 3.14, "string_key": "test"}
+ false  | {"lst": [1, null, 3.14, "string", {"key": "value"}, []]}
+ false  | {}
+ false  | {"i": 0}
+ false  | {"i": 1}
+ true   | {"id": "initial"}
+ true   | {"id": "middle"}
+ true   | {"id": "end"}
+ true   | {"id": "initial"}
+ true   | {"id": "middle"}
+ true   | {"id": "end"}
+ true   | {"id": "initial"}
+ true   | {"id": "middle"}
+ true   | {"id": "end"}
+ true   | {"id": "initial"}
+(15 rows)
+
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (u) RETURN EXISTS(u.name), properties(u) $$)
+AS (exists agtype, properties agtype);
+ exists |                                                 properties                                                  
+--------+-------------------------------------------------------------------------------------------------------------
+ false  | {"int_key": 1, "map_key": {"key": "value"}, "list_key": [1, 2, 3], "float_key": 3.14, "string_key": "test"}
+ false  | {"lst": [1, null, 3.14, "string", {"key": "value"}, []]}
+ false  | {}
+ false  | {"i": 0}
+ false  | {"i": 1}
+ false  | {"id": "initial"}
+ false  | {"id": "middle"}
+ false  | {"id": "end"}
+ false  | {"id": "initial"}
+ false  | {"id": "middle"}
+ false  | {"id": "end"}
+ false  | {"id": "initial"}
+ false  | {"id": "middle"}
+ false  | {"id": "end"}
+ false  | {"id": "initial"}
+(15 rows)
+
 -- should give an error
 SELECT * FROM cypher('cypher_match',
  $$MATCH (u) WHERE EXISTS(u) RETURN u$$)
diff --git a/regress/sql/cypher_match.sql b/regress/sql/cypher_match.sql
index aeed9f38..70f638df 100644
--- a/regress/sql/cypher_match.sql
+++ b/regress/sql/cypher_match.sql
@@ -372,6 +372,20 @@ SELECT * FROM cypher('cypher_match',
  $$MATCH (u)-[e]->(v) WHERE EXISTS((u)-[e]->(u)) AND EXISTS((v)-[e]->(v)) RETURN u, e, v $$)
 AS (u agtype, e agtype, v agtype);
 
+-- Return exists(pattern)
+
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (u) RETURN EXISTS((u)-[]->()) $$)
+AS (exists agtype);
+
+SELECT * FROM cypher('cypher_match',
+ $$MATCH p=(u)-[e]->(v) RETURN EXISTS((p)) $$)
+AS (exists agtype);
+
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (u)-[e]->(v) RETURN EXISTS((u)-[e]->(v)-[e]->(u))$$)
+AS (exists agtype);
+
 -- These should error
 -- Bad pattern
 SELECT * FROM cypher('cypher_match',
@@ -410,6 +424,15 @@ SELECT * FROM cypher('cypher_match',
  $$MATCH (u) WHERE EXISTS(u.id) AND EXISTS((u)-[]->(u)) RETURN u$$)
 AS (u agtype);
 
+-- Return exists(property)
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (u) RETURN EXISTS(u.id), properties(u) $$)
+AS (exists agtype, properties agtype);
+
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (u) RETURN EXISTS(u.name), properties(u) $$)
+AS (exists agtype, properties agtype);
+
 -- should give an error
 SELECT * FROM cypher('cypher_match',
  $$MATCH (u) WHERE EXISTS(u) RETURN u$$)
diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c
index 380eef92..e1e6c94b 100644
--- a/src/backend/parser/cypher_clause.c
+++ b/src/backend/parser/cypher_clause.c
@@ -5142,6 +5142,7 @@ transform_cypher_clause_as_subquery(cypher_parsestate *cpstate,
     Assert(pstate->p_expr_kind == EXPR_KIND_NONE ||
            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);
 
     /*