You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@age.apache.org by jo...@apache.org on 2022/05/06 21:38:58 UTC

[incubator-age] branch master updated: Improve Where clause performance and supoort Index Scans

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

joshinnis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-age.git


The following commit(s) were added to refs/heads/master by this push:
     new 379983b  Improve Where clause performance and supoort Index Scans
379983b is described below

commit 379983b0cefa92aff89eb8d641e05bb376c83862
Author: Josh Innis <Jo...@gmail.com>
AuthorDate: Fri May 6 14:28:15 2022 -0700

    Improve Where clause performance and supoort Index Scans
    
    Improve the evaluation of qual in the WHERE clause by referencing
    the properties column in the underlying table, rather than having
    to go through the constructed variable for the entity.
    
    Allow the access operator to be used in index construction.
    
    Modify the inderection logic to be capable of using indices,
    if they exist.
---
 Makefile                                     |   1 +
 age--1.0.0.sql                               |   2 +-
 regress/expected/index.out                   |  93 +++----
 regress/sql/index.sql                        |  67 +++--
 src/backend/parser/cypher_clause.c           | 372 ++++++++-------------------
 src/backend/parser/cypher_expr.c             | 193 +++++++++++---
 src/backend/parser/cypher_parse_node.c       |  26 ++
 src/backend/parser/cypher_transform_entity.c | 149 +++++++++++
 src/backend/utils/adt/agtype.c               |   2 +-
 src/include/parser/cypher_parse_node.h       |   3 +
 src/include/parser/cypher_transform_entity.h |  97 +++++++
 11 files changed, 636 insertions(+), 369 deletions(-)

diff --git a/Makefile b/Makefile
index 0745f07..deb3b4d 100644
--- a/Makefile
+++ b/Makefile
@@ -48,6 +48,7 @@ OBJS = src/backend/age.o \
        src/backend/parser/cypher_parse_agg.o \
        src/backend/parser/cypher_parse_node.o \
        src/backend/parser/cypher_parser.o \
+       src/backend/parser/cypher_transform_entity.o \
        src/backend/utils/adt/age_graphid_ds.o \
        src/backend/utils/adt/agtype.o \
        src/backend/utils/adt/agtype_ext.o \
diff --git a/age--1.0.0.sql b/age--1.0.0.sql
index 712dfcc..9a95e8f 100644
--- a/age--1.0.0.sql
+++ b/age--1.0.0.sql
@@ -3238,7 +3238,7 @@ CREATE CAST (agtype AS int[])
 CREATE FUNCTION ag_catalog.agtype_access_operator(VARIADIC agtype[])
 RETURNS agtype
 LANGUAGE c
-STABLE
+IMMUTABLE
 RETURNS NULL ON NULL INPUT
 PARALLEL SAFE
 AS 'MODULE_PATHNAME';
diff --git a/regress/expected/index.out b/regress/expected/index.out
index 05ed577..87c316e 100644
--- a/regress/expected/index.out
+++ b/regress/expected/index.out
@@ -226,14 +226,7 @@ SELECT * FROM cypher('cypher_index', $$ MATCH(n) DETACH DELETE n $$) AS (a agtyp
 /*
  * Section 2: Graphid Indices to Improve Join Performance
  */
-SELECT create_graph('agload_test_graph');
-NOTICE:  graph "agload_test_graph" has been created
- create_graph 
---------------
- 
-(1 row)
-
-SELECT * FROM cypher('agload_test_graph', $$
+SELECT * FROM cypher('cypher_index', $$
     CREATE (us:Country {name: "United States"}),
         (ca:Country {name: "Canada"}),
         (mx:Country {name: "Mexico"}),
@@ -252,28 +245,22 @@ $$) as (n agtype);
 ---
 (0 rows)
 
-ALTER TABLE agload_test_graph."Country" ADD PRIMARY KEY (id);
-CREATE UNIQUE INDEX CONCURRENTLY cntry_id_idx ON agload_test_graph."Country" (id);
-ALTER TABLE agload_test_graph."Country"  CLUSTER ON cntry_id_idx;
-ALTER TABLE agload_test_graph."City"
-ADD PRIMARY KEY (id);
-CREATE UNIQUE INDEX city_id_idx
-ON agload_test_graph."City" (id);
-ALTER TABLE agload_test_graph."City"
-CLUSTER ON city_id_idx;
-ALTER TABLE agload_test_graph.has_city
+ALTER TABLE cypher_index."Country" ADD PRIMARY KEY (id);
+CREATE UNIQUE INDEX CONCURRENTLY cntry_id_idx ON cypher_index."Country" (id);
+ALTER TABLE cypher_index."Country"  CLUSTER ON cntry_id_idx;
+ALTER TABLE cypher_index."City" ADD PRIMARY KEY (id);
+CREATE UNIQUE INDEX city_id_idx ON cypher_index."City" (id);
+ALTER TABLE cypher_index."City" CLUSTER ON city_id_idx;
+ALTER TABLE cypher_index.has_city
 ADD CONSTRAINT has_city_end_fk FOREIGN KEY (end_id)
-REFERENCES agload_test_graph."Country"(id) MATCH FULL;
-CREATE INDEX load_has_city_eid_idx
-ON agload_test_graph.has_city (end_id);
-CREATE INDEX load_has_city_sid_idx
-ON agload_test_graph.has_city (start_id);
-ALTER TABLE agload_test_graph."has_city"
-CLUSTER ON load_has_city_eid_idx;
+REFERENCES cypher_index."Country"(id) MATCH FULL;
+CREATE INDEX load_has_city_eid_idx ON cypher_index.has_city (end_id);
+CREATE INDEX load_has_city_sid_idx ON cypher_index.has_city (start_id);
+ALTER TABLE cypher_index."has_city" CLUSTER ON load_has_city_eid_idx;
 SET enable_mergejoin = ON;
 SET enable_hashjoin = OFF;
 SET enable_nestloop = OFF;
-SELECT COUNT(*) FROM cypher('agload_test_graph', $$
+SELECT COUNT(*) FROM cypher('cypher_index', $$
     MATCH (a:Country)<-[e:has_city]-()
     RETURN e
 $$) as (n agtype);
@@ -285,7 +272,7 @@ $$) as (n agtype);
 SET enable_mergejoin = OFF;
 SET enable_hashjoin = ON;
 SET enable_nestloop = OFF;
-SELECT COUNT(*) FROM cypher('agload_test_graph', $$
+SELECT COUNT(*) FROM cypher('cypher_index', $$
     MATCH (a:Country)<-[e:has_city]-()
     RETURN e
 $$) as (n agtype);
@@ -297,7 +284,7 @@ $$) as (n agtype);
 SET enable_mergejoin = OFF;
 SET enable_hashjoin = OFF;
 SET enable_nestloop = ON;
-SELECT COUNT(*) FROM cypher('agload_test_graph', $$
+SELECT COUNT(*) FROM cypher('cypher_index', $$
     MATCH (a:Country)<-[e:has_city]-()
     RETURN e
 $$) as (n agtype);
@@ -306,12 +293,15 @@ $$) as (n agtype);
     10
 (1 row)
 
+SET enable_mergejoin = ON;
+SET enable_hashjoin = ON;
+SET enable_nestloop = ON;
 --
 -- Section 3: Agtype GIN Indices to Improve WHERE clause Performance
 --
 CREATE INDEX load_city_gid_idx
-ON agload_test_graph."City" USING gin (properties);
-SELECT COUNT(*) FROM cypher('agload_test_graph', $$
+ON cypher_index."City" USING gin (properties);
+SELECT COUNT(*) FROM cypher('cypher_index', $$
     MATCH (c:City {country_code: "AD"})
     RETURN c
 $$) as (n agtype);
@@ -320,14 +310,42 @@ $$) as (n agtype);
      0
 (1 row)
 
+DROP INDEX load_city_gid_idx;
+ERROR:  index "load_city_gid_idx" does not exist
+--
+-- Section 4: Index use with WHERE clause
+--
+SELECT COUNT(*) FROM cypher('cypher_index', $$
+    MATCH (a:City)
+    WHERE a.country_code = 'RS'
+    RETURN a
+$$) as (n agtype);
+ count 
+-------
+     0
+(1 row)
+
+CREATE INDEX CONCURRENTLY cntry_ode_idx ON cypher_index."City"
+(ag_catalog.agtype_access_operator(properties, '"country_code"'::agtype));
+SELECT COUNT(*) FROM cypher('agload_test_graph', $$
+    MATCH (a:City)
+    WHERE a.country_code = 'RS'
+    RETURN a
+$$) as (n agtype);
+ERROR:  graph "agload_test_graph" does not exist
+LINE 1: SELECT COUNT(*) FROM cypher('agload_test_graph', $$
+                                    ^
 --
 -- General Cleanup
 --
 SELECT drop_graph('cypher_index', true);
-NOTICE:  drop cascades to 3 other objects
+NOTICE:  drop cascades to 6 other objects
 DETAIL:  drop cascades to table cypher_index._ag_label_vertex
 drop cascades to table cypher_index._ag_label_edge
 drop cascades to table cypher_index.idx
+drop cascades to table cypher_index."Country"
+drop cascades to table cypher_index.has_city
+drop cascades to table cypher_index."City"
 NOTICE:  graph "cypher_index" has been dropped
  drop_graph 
 ------------
@@ -335,15 +353,4 @@ NOTICE:  graph "cypher_index" has been dropped
 (1 row)
 
 SELECT drop_graph('agload_test_graph', true);
-NOTICE:  drop cascades to 5 other objects
-DETAIL:  drop cascades to table agload_test_graph._ag_label_vertex
-drop cascades to table agload_test_graph._ag_label_edge
-drop cascades to table agload_test_graph."Country"
-drop cascades to table agload_test_graph.has_city
-drop cascades to table agload_test_graph."City"
-NOTICE:  graph "agload_test_graph" has been dropped
- drop_graph 
-------------
- 
-(1 row)
-
+ERROR:  graph "agload_test_graph" does not exist
diff --git a/regress/sql/index.sql b/regress/sql/index.sql
index 62695f3..0495db0 100644
--- a/regress/sql/index.sql
+++ b/regress/sql/index.sql
@@ -130,9 +130,7 @@ SELECT * FROM cypher('cypher_index', $$ MATCH(n) DETACH DELETE n $$) AS (a agtyp
 /*
  * Section 2: Graphid Indices to Improve Join Performance
  */
-SELECT create_graph('agload_test_graph');
-
-SELECT * FROM cypher('agload_test_graph', $$
+SELECT * FROM cypher('cypher_index', $$
     CREATE (us:Country {name: "United States"}),
         (ca:Country {name: "Canada"}),
         (mx:Country {name: "Mexico"}),
@@ -148,39 +146,32 @@ SELECT * FROM cypher('agload_test_graph', $$
         (mx)<-[:has_city]-(:City {name:"Tijuana", country_code:"MX"})
 $$) as (n agtype);
 
-ALTER TABLE agload_test_graph."Country" ADD PRIMARY KEY (id);
+ALTER TABLE cypher_index."Country" ADD PRIMARY KEY (id);
 
-CREATE UNIQUE INDEX CONCURRENTLY cntry_id_idx ON agload_test_graph."Country" (id);
-ALTER TABLE agload_test_graph."Country"  CLUSTER ON cntry_id_idx;
+CREATE UNIQUE INDEX CONCURRENTLY cntry_id_idx ON cypher_index."Country" (id);
+ALTER TABLE cypher_index."Country"  CLUSTER ON cntry_id_idx;
 
-ALTER TABLE agload_test_graph."City"
-ADD PRIMARY KEY (id);
+ALTER TABLE cypher_index."City" ADD PRIMARY KEY (id);
 
-CREATE UNIQUE INDEX city_id_idx
-ON agload_test_graph."City" (id);
+CREATE UNIQUE INDEX city_id_idx ON cypher_index."City" (id);
 
-ALTER TABLE agload_test_graph."City"
-CLUSTER ON city_id_idx;
+ALTER TABLE cypher_index."City" CLUSTER ON city_id_idx;
 
-ALTER TABLE agload_test_graph.has_city
+ALTER TABLE cypher_index.has_city
 ADD CONSTRAINT has_city_end_fk FOREIGN KEY (end_id)
-REFERENCES agload_test_graph."Country"(id) MATCH FULL;
-
-CREATE INDEX load_has_city_eid_idx
-ON agload_test_graph.has_city (end_id);
+REFERENCES cypher_index."Country"(id) MATCH FULL;
 
-CREATE INDEX load_has_city_sid_idx
-ON agload_test_graph.has_city (start_id);
+CREATE INDEX load_has_city_eid_idx ON cypher_index.has_city (end_id);
 
-ALTER TABLE agload_test_graph."has_city"
-CLUSTER ON load_has_city_eid_idx;
+CREATE INDEX load_has_city_sid_idx ON cypher_index.has_city (start_id);
 
+ALTER TABLE cypher_index."has_city" CLUSTER ON load_has_city_eid_idx;
 
 SET enable_mergejoin = ON;
 SET enable_hashjoin = OFF;
 SET enable_nestloop = OFF;
 
-SELECT COUNT(*) FROM cypher('agload_test_graph', $$
+SELECT COUNT(*) FROM cypher('cypher_index', $$
     MATCH (a:Country)<-[e:has_city]-()
     RETURN e
 $$) as (n agtype);
@@ -189,7 +180,7 @@ SET enable_mergejoin = OFF;
 SET enable_hashjoin = ON;
 SET enable_nestloop = OFF;
 
-SELECT COUNT(*) FROM cypher('agload_test_graph', $$
+SELECT COUNT(*) FROM cypher('cypher_index', $$
     MATCH (a:Country)<-[e:has_city]-()
     RETURN e
 $$) as (n agtype);
@@ -198,22 +189,46 @@ SET enable_mergejoin = OFF;
 SET enable_hashjoin = OFF;
 SET enable_nestloop = ON;
 
-SELECT COUNT(*) FROM cypher('agload_test_graph', $$
+SELECT COUNT(*) FROM cypher('cypher_index', $$
     MATCH (a:Country)<-[e:has_city]-()
     RETURN e
 $$) as (n agtype);
 
+SET enable_mergejoin = ON;
+SET enable_hashjoin = ON;
+SET enable_nestloop = ON;
+
 --
 -- Section 3: Agtype GIN Indices to Improve WHERE clause Performance
 --
 CREATE INDEX load_city_gid_idx
-ON agload_test_graph."City" USING gin (properties);
+ON cypher_index."City" USING gin (properties);
 
-SELECT COUNT(*) FROM cypher('agload_test_graph', $$
+SELECT COUNT(*) FROM cypher('cypher_index', $$
     MATCH (c:City {country_code: "AD"})
     RETURN c
 $$) as (n agtype);
 
+DROP INDEX load_city_gid_idx;
+
+--
+-- Section 4: Index use with WHERE clause
+--
+SELECT COUNT(*) FROM cypher('cypher_index', $$
+    MATCH (a:City)
+    WHERE a.country_code = 'RS'
+    RETURN a
+$$) as (n agtype);
+
+CREATE INDEX CONCURRENTLY cntry_ode_idx ON cypher_index."City"
+(ag_catalog.agtype_access_operator(properties, '"country_code"'::agtype));
+
+SELECT COUNT(*) FROM cypher('agload_test_graph', $$
+    MATCH (a:City)
+    WHERE a.country_code = 'RS'
+    RETURN a
+$$) as (n agtype);
+
 --
 -- General Cleanup
 --
diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c
index a59c6d3..f74f0c4 100644
--- a/src/backend/parser/cypher_clause.c
+++ b/src/backend/parser/cypher_clause.c
@@ -59,6 +59,7 @@
 #include "parser/cypher_item.h"
 #include "parser/cypher_parse_agg.h"
 #include "parser/cypher_parse_node.h"
+#include "parser/cypher_transform_entity.h"
 #include "utils/ag_cache.h"
 #include "utils/ag_func.h"
 #include "utils/agtype.h"
@@ -84,66 +85,6 @@
 #define AGE_VARNAME_ID AGE_DEFAULT_VARNAME_PREFIX"id"
 #define AGE_VARNAME_SET_CLAUSE AGE_DEFAULT_VARNAME_PREFIX"set_clause"
 
-enum transform_entity_type
-{
-    ENT_VERTEX = 0x0,
-    ENT_EDGE,
-    ENT_VLE_EDGE
-};
-
-enum transform_entity_join_side
-{
-    JOIN_SIDE_LEFT = 0x0,
-    JOIN_SIDE_RIGHT
-};
-
-/*
- * In the transformation stage, we need to track
- * where a variable came from. When moving between
- * clauses, Postgres parsestate and Query data structures
- * are insufficient for some of the information we
- * need.
- */
-typedef struct
-{
-    // denotes whether this entity is a vertex or edge
-    enum transform_entity_type type;
-
-    /*
-     * MATCH clauses are transformed into a select * FROM ... JOIN, etc
-     * We need to know wheter the table that this entity represents is
-     * part of the join tree. If a cypher_node does not meet the conditions
-     * set in INCLUDE_NODE_IN_JOIN_TREE. Then we can skip the node when
-     * constructing our join tree. The entities around this particular entity
-     * need to know this for the join to get properly constructed.
-     */
-    bool in_join_tree;
-
-    /*
-     * The parse data structure will be transformed into an Expr that represents
-     * the entity. When contructing the join tree, we need to know what it was
-     * turned into. If the entity was originally created in a previous clause,
-     * this will be a Var that we need to reference to extract the id, startid,
-     * endid for the join. If the entity was created in the current clause, then
-     * this will be a FuncExpr that we can reference to get the id, startid, and
-     * endid.
-     */
-    Expr *expr;
-
-    /*
-     * tells each clause whether this variable was
-     * declared by itself or a previous clause.
-     */
-    bool declared_in_current_clause;
-
-    // The parse data structure that we transformed
-    union
-    {
-        cypher_node *node;
-        cypher_relationship *rel;
-    } entity;
-} transform_entity;
-
 /*
  * Rules to determine if a node must be included:
  *
@@ -173,8 +114,7 @@ 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,
-                                                 Node *where);
+                                                 cypher_clause *clause);
 // match clause
 static Query *transform_cypher_match(cypher_parsestate *cpstate,
                                      cypher_clause *clause);
@@ -183,7 +123,7 @@ static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate,
 static List *transform_match_entities(cypher_parsestate *cpstate, Query *query,
                                       cypher_path *path);
 static void transform_match_pattern(cypher_parsestate *cpstate, Query *query,
-                                    List *pattern);
+                                    List *pattern, Node *where);
 static List *transform_match_path(cypher_parsestate *cpstate, Query *query,
                                   cypher_path *path);
 static Expr *transform_cypher_edge(cypher_parsestate *cpstate,
@@ -220,10 +160,6 @@ static List *make_edge_quals(cypher_parsestate *cpstate,
                              enum transform_entity_join_side side);
 static A_Expr *filter_vertices_on_label_id(cypher_parsestate *cpstate,
                                            Node *id_field, char *label);
-static transform_entity *
-make_transform_entity(cypher_parsestate *cpstate,
-                      enum transform_entity_type type, Node *node, Expr *expr);
-static transform_entity *find_variable(cypher_parsestate *cpstate, char *name);
 static Node *create_property_constraints(cypher_parsestate *cpstate,
                                          transform_entity *entity,
                                          Node *property_constraints);
@@ -331,11 +267,6 @@ transform_cypher_merge_mark_tuple_position(List *target_list,
 #define transform_prev_cypher_clause(cpstate, prev_clause, add_rte_to_query) \
     transform_cypher_clause_as_subquery(cpstate, transform_cypher_clause, \
                                         prev_clause, NULL, add_rte_to_query)
-static char *get_next_default_alias(cypher_parsestate *cpstate);
-static transform_entity *find_transform_entity(cypher_parsestate *cpstate,
-                                               char *name,
-                                               enum transform_entity_type type);
-
 static RangeTblEntry *transform_cypher_clause_as_subquery(cypher_parsestate *cpstate,
                                                           transform_method transform,
                                                           cypher_clause *clause,
@@ -370,7 +301,6 @@ static List *make_target_list_from_join(ParseState *pstate,
 static Expr *add_volatile_wrapper(Expr *node);
 static FuncExpr *make_clause_func_expr(char *function_name,
                                        Node *clause_information);
-static char *get_entity_name(transform_entity *entity);
 /* for VLE support */
 static RangeTblEntry *transform_RangeFunction(cypher_parsestate *cpstate,
                                               RangeFunction *r);
@@ -1988,22 +1918,22 @@ static Query *transform_cypher_with(cypher_parsestate *cpstate,
     wrapper->prev = clause->prev;
 
     return transform_cypher_clause_with_where(cpstate, transform_cypher_return,
-                                              wrapper, self->where);
+                                              wrapper);
 }
 
 static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate,
                                                  transform_method transform,
-                                                 cypher_clause *clause,
-                                                 Node *where)
+                                                 cypher_clause *clause)
 {
     ParseState *pstate = (ParseState *)cpstate;
     Query *query;
+    cypher_match *self = (cypher_match *)clause->self;
+    Node *where = self->where;
 
     if (where)
     {
         RangeTblEntry *rte;
         int rtindex;
-        Node *qual;
 
         query = makeNode(Query);
         query->commandType = CMD_SELECT;
@@ -2017,12 +1947,8 @@ static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate,
 
         markTargetListOrigins(pstate, query->targetList);
 
-        // see transformWhereClause()
-        qual = transform_cypher_expr(cpstate, where, EXPR_KIND_WHERE);
-        qual = coerce_to_boolean(pstate, qual, "WHERE");
-
         query->rtable = pstate->p_rtable;
-        query->jointree = makeFromExpr(pstate->p_joinlist, qual);
+        query->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
         assign_query_collations(pstate, query);
     }
@@ -2041,10 +1967,8 @@ static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate,
 static Query *transform_cypher_match(cypher_parsestate *cpstate,
                                      cypher_clause *clause)
 {
-    cypher_match *self = (cypher_match *)clause->self;
-
     return transform_cypher_clause_with_where(
-        cpstate, transform_cypher_match_pattern, clause, self->where);
+        cpstate, transform_cypher_match_pattern, clause);
 }
 
 /*
@@ -2212,6 +2136,7 @@ static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate,
     ParseState *pstate = (ParseState *)cpstate;
     cypher_match *self = (cypher_match *)clause->self;
     Query *query;
+    Node *where = self->where;
 
     query = makeNode(Query);
     query->commandType = CMD_SELECT;
@@ -2224,7 +2149,6 @@ static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate,
         query->targetList = make_target_list_from_join(pstate, rte);
         query->rtable = pstate->p_rtable;
         query->jointree = makeFromExpr(pstate->p_joinlist, NULL);
-        query->hasSubLinks = pstate->p_hasSubLinks;
     }
     else
     {
@@ -2245,11 +2169,16 @@ static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate,
             query->targetList = expandRelAttrs(pstate, rte, rtindex, 0, -1);
         }
 
-        transform_match_pattern(cpstate, query, self->pattern);
+        transform_match_pattern(cpstate, query, self->pattern, where);
     }
 
     markTargetListOrigins(pstate, query->targetList);
 
+    query->hasSubLinks = pstate->p_hasSubLinks;
+    query->hasWindowFuncs = pstate->p_hasWindowFuncs;
+    query->hasTargetSRFs = pstate->p_hasTargetSRFs;
+    query->hasAggs = pstate->p_hasAggs;
+
     assign_query_collations(pstate, query);
 
     return query;
@@ -2578,8 +2507,9 @@ static RangeTblEntry *transform_RangeFunction(cypher_parsestate *cpstate,
 }
 
 static void transform_match_pattern(cypher_parsestate *cpstate, Query *query,
-                                    List *pattern)
+                                    List *pattern, Node *where)
 {
+    ParseState *pstate = (ParseState *)cpstate;
     ListCell *lc;
     List *quals = NIL;
     Expr *q = NULL;
@@ -2623,30 +2553,35 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query,
         }
     }
 
-    query->rtable = cpstate->pstate.p_rtable;
-    query->jointree = makeFromExpr(cpstate->pstate.p_joinlist, (Node *)expr);
-}
-
-static char *get_next_default_alias(cypher_parsestate *cpstate)
-{
-    char *alias_name;
-    int nlen = 0;
-
-    /* get the length of the combinded string */
-    nlen = snprintf(NULL, 0, "%s%d", AGE_DEFAULT_ALIAS_PREFIX,
-                    cpstate->default_alias_num);
+    // transform the where clause quals and add to the quals,
+    if (where != NULL)
+    {
+        Expr *where_qual;
 
-    /* allocate the space */
-    alias_name = palloc0(nlen + 1);
+        where_qual = (Expr *)transform_cypher_expr(cpstate, where,
+                                                   EXPR_KIND_WHERE);
 
-    /* create the name */
-    snprintf(alias_name, nlen + 1, "%s%d", AGE_DEFAULT_ALIAS_PREFIX,
-             cpstate->default_alias_num);
+        if (quals == NIL)
+        {
+            expr = where_qual;
+        }
+        else
+        {
+            expr = makeBoolExpr(AND_EXPR, list_make2(expr, where_qual), -1);
+        }
+    }
 
-    /* increment the default alias number */
-    cpstate->default_alias_num++;
+    /*
+     * Coerce to WHERE clause to a bool, denoting whether the constructed
+     * clause is true or false.
+     */
+    if (expr != NULL)
+    {
+        expr = (Expr *)coerce_to_boolean(pstate, (Node *)expr, "WHERE");
+    }
 
-    return alias_name;
+    query->rtable = cpstate->pstate.p_rtable;
+    query->jointree = makeFromExpr(cpstate->pstate.p_joinlist, (Node *)expr);
 }
 
 /*
@@ -3165,88 +3100,6 @@ static A_Expr *filter_vertices_on_label_id(cypher_parsestate *cpstate,
     return makeSimpleA_Expr(AEXPR_OP, "=", (Node *)fc, (Node *)n, -1);
 }
 
-static transform_entity *make_transform_entity(cypher_parsestate *cpstate,
-                                               enum transform_entity_type type,
-                                               Node *node, Expr *expr)
-{
-    transform_entity *entity;
-
-    entity = palloc(sizeof(transform_entity));
-
-    entity->type = type;
-    if (type == ENT_VERTEX)
-    {
-        entity->entity.node = (cypher_node *)node;
-    }
-    else if (entity->type == ENT_EDGE || entity->type == ENT_VLE_EDGE)
-    {
-        entity->entity.rel = (cypher_relationship *)node;
-    }
-    else
-    {
-        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                        errmsg("unknown entity type")));
-    }
-
-    entity->declared_in_current_clause = true;
-    entity->expr = expr;
-    entity->in_join_tree = expr != NULL;
-
-    return entity;
-}
-
-static transform_entity *find_variable(cypher_parsestate *cpstate, char *name)
-{
-    ListCell *lc;
-
-    foreach (lc, cpstate->entities)
-    {
-        transform_entity *entity = lfirst(lc);
-        char *entity_name;
-
-        if (entity->type == ENT_VERTEX)
-        {
-            entity_name = entity->entity.node->name;
-        }
-        else if (entity->type == ENT_EDGE)
-        {
-            entity_name = entity->entity.rel->name;
-        }
-        else
-        {
-            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                            errmsg("unknown entity type")));
-        }
-
-        if (entity_name != NULL && !strcmp(name, entity_name))
-        {
-            return entity;
-        }
-    }
-
-    return NULL;
-}
-
-static char *get_entity_name(transform_entity *entity)
-{
-    if (entity->type == ENT_EDGE || entity->type == ENT_VLE_EDGE)
-    {
-        return entity->entity.rel->name;
-    }
-    else if (entity->type == ENT_VERTEX)
-    {
-        return entity->entity.node->name;
-    }
-    else
-    {
-        ereport(ERROR,
-                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                        errmsg("cannot get entity name from transform_entity type %i", entity->type)));
-    }
-
-    return NULL;
-}
-
 /*
  * Creates the Contains operator to process property contraints for a vertex/
  * edge in a MATCH clause. creates the agtype @> with the enitity's properties
@@ -3899,9 +3752,39 @@ static Expr *transform_cypher_edge(cypher_parsestate *cpstate,
 
     if (rel->name != NULL)
     {
-        TargetEntry *te = findTarget(*target_list, rel->name);
+        TargetEntry *te;
+        Node *expr;
+
+        /*
+         * If we are in a WHERE clause transform, we don't want to create new
+         * variables, we want to use the existing ones. So, error if otherwise.
+         */
+        if (pstate->p_expr_kind == EXPR_KIND_WHERE)
+        {
+            cypher_parsestate *parent_cpstate =
+               (cypher_parsestate *)pstate->parentParseState->parentParseState;
+            /*
+             *  If expr_kind is WHERE, the expressions are in the parent's
+             *  parent's parsestate, due to the way we transform sublinks.
+             */
+            transform_entity *entity = find_variable(parent_cpstate, rel->name);
+
+            if (entity != NULL)
+            {
+                return entity->expr;
+            }
+            else
+            {
+                ereport(ERROR,
+                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                         errmsg("variable `%s` does not exist", rel->name),
+                         parser_errposition(pstate, rel->location)));
+            }
+        }
+
+        te = findTarget(*target_list, rel->name);
         /* also search for a variable from a previous transform */
-        Node *expr = colNameToVar(pstate, rel->name, false, rel->location);
+        expr = colNameToVar(pstate, rel->name, false, rel->location);
 
         if (expr != NULL)
         {
@@ -3929,18 +3812,6 @@ static Expr *transform_cypher_edge(cypher_parsestate *cpstate,
             }
             return te->expr;
         }
-
-        /*
-         * If we are in a WHERE clause transform, we don't want to create new
-         * variables, we want to use the existing ones. So, error if otherwise.
-         */
-        if (pstate->p_expr_kind == EXPR_KIND_WHERE)
-        {
-            ereport(ERROR,
-                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                     errmsg("variable %s does not exist", rel->name),
-                     parser_errposition(pstate, rel->location)));
-        }
     }
 
     if (!rel->name)
@@ -4025,9 +3896,40 @@ static Expr *transform_cypher_node(cypher_parsestate *cpstate,
 
     if (node->name != NULL)
     {
-        TargetEntry *te = findTarget(*target_list, node->name);
+        TargetEntry *te;
+        Node *expr;
+
+        /*
+         * If we are in a WHERE clause transform, we don't want to create new
+         * variables, we want to use the existing ones. So, error if otherwise.
+         */
+        if (pstate->p_expr_kind == EXPR_KIND_WHERE)
+        {
+            cypher_parsestate *parent_cpstate =
+               (cypher_parsestate *)pstate->parentParseState->parentParseState;
+            /*
+             *  If expr_kind is WHERE, the expressions are in the parent's
+             *  parent's parsestate, due to the way we transform sublinks.
+             */
+            transform_entity *entity = find_variable(parent_cpstate, node->name);
+
+
+            if (entity != NULL)
+            {
+                return entity->expr;
+            }
+            else
+            {
+                ereport(ERROR,
+                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                        errmsg("variable `%s` does not exist", node->name),
+                        parser_errposition(pstate, node->location)));
+            }
+        }
+
+        te = findTarget(*target_list, node->name);
         /* also search for the variable from a previous transforms */
-        Node *expr = colNameToVar(pstate, node->name, false, node->location);
+        expr = colNameToVar(pstate, node->name, false, node->location);
 
         if (expr != NULL)
         {
@@ -4056,18 +3958,6 @@ static Expr *transform_cypher_node(cypher_parsestate *cpstate,
 
             return te->expr;
         }
-
-        /*
-         * If we are in a WHERE clause transform, we don't want to create new
-         * variables, we want to use the existing ones. So, error if otherwise.
-         */
-        if (pstate->p_expr_kind == EXPR_KIND_WHERE)
-        {
-            ereport(ERROR,
-                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                     errmsg("variable `%s` does not exist", node->name),
-                     parser_errposition(pstate, node->location)));
-        }
     }
     else
     {
@@ -4822,7 +4712,7 @@ transform_cypher_clause_as_subquery(cypher_parsestate *cpstate,
     }
     else if (pstate->p_expr_kind == EXPR_KIND_OTHER)
     {
-	// this is a lateral subselect for the MERGE
+        // this is a lateral subselect for the MERGE
         pstate->p_expr_kind = EXPR_KIND_FROM_SUBSELECT;
         lateral = true;
     }
@@ -5415,7 +5305,7 @@ transform_merge_cypher_edge(cypher_parsestate *cpstate, List **target_list,
                  parser_errposition(pstate, edge->location)));
         }
 
-	rel->flags |= CYPHER_TARGET_NODE_IS_VAR;
+        rel->flags |= CYPHER_TARGET_NODE_IS_VAR;
     }
     else
     {
@@ -5614,44 +5504,6 @@ static cypher_clause *convert_merge_to_match(cypher_merge *merge)
     return clause;
 }
 
-/*
- * Finds the transform_entity in the cypher_parstate for a the given name and
- * type.
- */
-static transform_entity *find_transform_entity(cypher_parsestate *cpstate,
-                                               char *name,
-                                               enum transform_entity_type type)
-{
-    ListCell *lc;
-
-    foreach(lc, cpstate->entities)
-    {
-        transform_entity *entity = lfirst(lc);
-
-        if (entity->type != type)
-        {
-            continue;
-        }
-
-        if (type == ENT_VERTEX)
-        {
-            if (!strcmp(entity->entity.node->name, name))
-            {
-                return entity;
-            }
-        }
-        else if (type == ENT_EDGE || type == ENT_VLE_EDGE)
-        {
-            if (!strcmp(entity->entity.rel->name, name))
-            {
-                return entity;
-            }
-        }
-    }
-
-    return NULL;
-}
-
 /*
  * Creates a namespace item for the given rte. boolean arguements will
  * let the rest of the ParseState know if the relation and/or columns are
@@ -5660,9 +5512,9 @@ static transform_entity *find_transform_entity(cypher_parsestate *cpstate,
  */
 static ParseNamespaceItem *create_namespace_item(RangeTblEntry *rte,
                                                  bool p_rel_visible,
-					         bool p_cols_visible,
+                                                 bool p_cols_visible,
                                                  bool p_lateral_only,
-					         bool p_lateral_ok)
+                                                 bool p_lateral_ok)
 {
     ParseNamespaceItem *nsitem;
 
diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c
index 2af3db7..914ab29 100644
--- a/src/backend/parser/cypher_expr.c
+++ b/src/backend/parser/cypher_expr.c
@@ -49,6 +49,7 @@
 #include "nodes/cypher_nodes.h"
 #include "parser/cypher_expr.h"
 #include "parser/cypher_parse_node.h"
+#include "parser/cypher_transform_entity.h"
 #include "utils/ag_func.h"
 #include "utils/agtype.h"
 
@@ -92,7 +93,9 @@ static Node *transform_SubLink(cypher_parsestate *cpstate, SubLink *sublink);
 static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn);
 static Node *transform_WholeRowRef(ParseState *pstate, RangeTblEntry *rte,
                                    int location);
-
+static ArrayExpr *make_agtype_array_expr(List *args);
+static Node *transform_column_ref_for_indirection(cypher_parsestate *cpstate,
+                                                  ColumnRef *cr);
 /* transform a cypher expression */
 Node *transform_cypher_expr(cypher_parsestate *cpstate, Node *expr,
                             ParseExprKind expr_kind)
@@ -302,6 +305,7 @@ static Node *transform_ColumnRef(cypher_parsestate *cpstate, ColumnRef *cref)
     {
         case 1:
             {
+                transform_entity *te;
                 field1 = (Node*)linitial(cref->fields);
 
                 Assert(IsA(field1, String));
@@ -309,43 +313,53 @@ static Node *transform_ColumnRef(cypher_parsestate *cpstate, ColumnRef *cref)
 
                 /* Try to identify as an unqualified column */
                 node = colNameToVar(pstate, colname, false, cref->location);
+                if (node != NULL)
+                {
+                        break;
+                }
 
-                if (node == NULL)
+                /*
+                 * Try to find the columnRef as a transform_entity and extract
+                 * the expr.
+                 */
+                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
+                 * relations already entered into the rangetable will be
+                 * recognized.
+                 *
+                 * This is a hack for backwards compatibility with
+                 * PostQUEL-inspired syntax.  The preferred form now is
+                 * "rel.*".
+                 */
+                rte = refnameRangeTblEntry(pstate, NULL, colname,
+                                           cref->location, &levels_up);
+                if (rte)
+                {
+                    node = transform_WholeRowRef(pstate, rte, cref->location);
+                }
+                else
                 {
-                    /*
-                     * Not known as a column of any range-table entry.
-                     * Try to find the name as a relation.  Note that only
-                     * relations already entered into the rangetable will be
-                     * recognized.
-                     *
-                     * This is a hack for backwards compatibility with
-                     * PostQUEL-inspired syntax.  The preferred form now is
-                     * "rel.*".
-                     */
-                    rte = refnameRangeTblEntry(pstate, NULL, colname,
-                                               cref->location, &levels_up);
-                    if (rte)
-                    {
-                        node = transform_WholeRowRef(pstate, rte,
-                                                     cref->location);
-                    }
-                    else
-                    {
-                        ereport(ERROR,
+                    ereport(ERROR,
                                 (errcode(ERRCODE_UNDEFINED_COLUMN),
                                  errmsg("could not find rte for %s", colname),
                                  parser_errposition(pstate, cref->location)));
-                    }
-
-                    if (node == NULL)
-                    {
-                        ereport(ERROR,
-                                (errcode(ERRCODE_DATA_EXCEPTION),
-                                 errmsg("unable to transform whole row for %s",
-                                         colname),
-                                 parser_errposition(pstate, cref->location)));
-                    }
                 }
+
+                if (node == NULL)
+                {
+                    ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION),
+                            errmsg("unable to transform whole row for %s", colname),
+                             parser_errposition(pstate, cref->location)));
+                }
+
                 break;
             }
         case 2:
@@ -617,9 +631,13 @@ static Node *transform_cypher_map(cypher_parsestate *cpstate, cypher_map *cm)
     }
 
     if (list_length(newkeyvals) == 0)
+    {
         func_oid = get_ag_func_oid("agtype_build_map", 0);
+    }
     else
+    {
         func_oid = get_ag_func_oid("agtype_build_map", 1, ANYOID);
+    }
 
     fexpr = makeFuncExpr(func_oid, AGTYPEOID, newkeyvals, InvalidOid,
                          InvalidOid, COERCE_EXPLICIT_CALL);
@@ -645,9 +663,13 @@ static Node *transform_cypher_list(cypher_parsestate *cpstate, cypher_list *cl)
     }
 
     if (list_length(newelems) == 0)
+    {
         func_oid = get_ag_func_oid("agtype_build_list", 0);
+    }
     else
+    {
         func_oid = get_ag_func_oid("agtype_build_list", 1, ANYOID);
+    }
 
     fexpr = makeFuncExpr(func_oid, AGTYPEOID, newelems, InvalidOid, InvalidOid,
                          COERCE_EXPLICIT_CALL);
@@ -656,6 +678,73 @@ static Node *transform_cypher_list(cypher_parsestate *cpstate, cypher_list *cl)
     return (Node *)fexpr;
 }
 
+// makes a VARIADIC agtype array
+static ArrayExpr *make_agtype_array_expr(List *args)
+{
+    ArrayExpr  *newa = makeNode(ArrayExpr);
+
+    newa->elements = args;
+
+    /* assume all the variadic arguments were coerced to the same type */
+    newa->element_typeid = AGTYPEOID;
+    newa->array_typeid = AGTYPEARRAYOID;
+
+    if (!OidIsValid(newa->array_typeid))
+    {
+        ereport(ERROR,
+                (errcode(ERRCODE_UNDEFINED_OBJECT),
+                 errmsg("could not find array type for data type %s",
+                        format_type_be(newa->element_typeid))));
+    }
+
+    /* array_collid will be set by parse_collate.c */
+    newa->multidims = false;
+
+    return newa;
+}
+
+/*
+ * Transforms a column ref for indirection. Try to find the rte that the
+ * columnRef is references and pass the properties of that rte as what the
+ * columnRef is referencing. Otherwise, reference the Var
+ */
+static Node *transform_column_ref_for_indirection(cypher_parsestate *cpstate,
+                                                  ColumnRef *cr)
+{
+    ParseState *pstate = (ParseState *)cpstate;
+    RangeTblEntry *rte = NULL;
+    Node *field1 = linitial(cr->fields);
+    char *relname = NULL;
+    Node *node = NULL;
+
+    Assert(IsA(field1, String));
+    relname = strVal(field1);
+
+    // locate the referenced RTE
+    rte = find_rte(cpstate, relname);
+    if (rte == NULL)
+    {
+        /*
+         * This column ref is referencing something that was created in
+         * a previous query and is a variable.
+         */
+        return transform_cypher_expr_recurse(cpstate, (Node *)cr);
+    }
+
+    // try to identify the properties column of the RTE
+    node = scanRTEForColumn(pstate, rte, "properties", cr->location, 0, NULL);
+
+    if (node == NULL)
+    {
+        ereport(ERROR,
+                (errcode(ERRCODE_UNDEFINED_OBJECT),
+                 errmsg("could not find rte for %s", relname)));
+
+    }
+
+    return node;
+}
+
 static Node *transform_A_Indirection(cypher_parsestate *cpstate,
                                      A_Indirection *a_ind)
 {
@@ -677,7 +766,17 @@ static Node *transform_A_Indirection(cypher_parsestate *cpstate,
     func_slice_oid = get_ag_func_oid("agtype_access_slice", 3, AGTYPEOID,
                                      AGTYPEOID, AGTYPEOID);
 
-    ind_arg_expr = transform_cypher_expr_recurse(cpstate, a_ind->arg);
+    if (IsA(a_ind->arg, ColumnRef))
+    {
+        ColumnRef *cr = (ColumnRef *)a_ind->arg;
+
+        ind_arg_expr = transform_column_ref_for_indirection(cpstate, cr);
+    }
+    else
+    {
+        ind_arg_expr = transform_cypher_expr_recurse(cpstate, a_ind->arg);
+    }
+
     location = exprLocation(ind_arg_expr);
 
     args = lappend(args, ind_arg_expr);
@@ -693,13 +792,18 @@ static Node *transform_A_Indirection(cypher_parsestate *cpstate,
             /* were we working on an access? if so, wrap and close it */
             if (is_access)
             {
-                func_expr = makeFuncExpr(func_access_oid, AGTYPEOID, args,
+                ArrayExpr *newa = make_agtype_array_expr(args);
+
+                func_expr = makeFuncExpr(func_access_oid, AGTYPEOID,
+                                         list_make1(newa),
                                          InvalidOid, InvalidOid,
                                          COERCE_EXPLICIT_CALL);
-                func_expr->location = location;
-                args = lappend(NIL, func_expr);
+
                 /* we are no longer working on an access */
                 is_access = false;
+
+                func_expr->funcvariadic = true;
+
             }
             /* add slice bounds to args */
             if (!indices->lidx)
@@ -710,8 +814,12 @@ static Node *transform_A_Indirection(cypher_parsestate *cpstate,
                 node = transform_cypher_expr_recurse(cpstate, (Node *)n);
             }
             else
+            {
                 node = transform_cypher_expr_recurse(cpstate, indices->lidx);
+            }
+
             args = lappend(args, node);
+
             if (!indices->uidx)
             {
                 A_Const *n = makeNode(A_Const);
@@ -720,7 +828,9 @@ static Node *transform_A_Indirection(cypher_parsestate *cpstate,
                 node = transform_cypher_expr_recurse(cpstate, (Node *)n);
             }
             else
+            {
                 node = transform_cypher_expr_recurse(cpstate, indices->uidx);
+            }
             args = lappend(args, node);
             /* wrap and close it */
             func_expr = makeFuncExpr(func_slice_oid, AGTYPEOID, args,
@@ -761,8 +871,15 @@ static Node *transform_A_Indirection(cypher_parsestate *cpstate,
 
     /* if we were doing an access, we need wrap the args with access func. */
     if (is_access)
-        func_expr = makeFuncExpr(func_access_oid, AGTYPEOID, args, InvalidOid,
-                                 InvalidOid, COERCE_EXPLICIT_CALL);
+    {
+        ArrayExpr *newa = make_agtype_array_expr(args);
+
+        func_expr = makeFuncExpr(func_access_oid, AGTYPEOID, list_make1(newa),
+                                 InvalidOid, InvalidOid,
+                                 COERCE_EXPLICIT_CALL);
+        func_expr->funcvariadic = true;
+    }
+
     Assert(func_expr != NULL);
     func_expr->location = location;
 
diff --git a/src/backend/parser/cypher_parse_node.c b/src/backend/parser/cypher_parse_node.c
index 5858c30..269dd3c 100644
--- a/src/backend/parser/cypher_parse_node.c
+++ b/src/backend/parser/cypher_parse_node.c
@@ -124,3 +124,29 @@ RangeTblEntry *find_rte(cypher_parsestate *cpstate, char *varname)
 
     return NULL;
 }
+
+/*
+ * Generates a default alias name for when a query needs on and the parse
+ * state does not provide one.
+ */
+char *get_next_default_alias(cypher_parsestate *cpstate)
+{
+    char *alias_name;
+    int nlen = 0;
+
+    /* get the length of the combinded string */
+    nlen = snprintf(NULL, 0, "%s%d", AGE_DEFAULT_ALIAS_PREFIX,
+                    cpstate->default_alias_num);
+
+    /* allocate the space */
+    alias_name = palloc0(nlen + 1);
+
+    /* create the name */
+    snprintf(alias_name, nlen + 1, "%s%d", AGE_DEFAULT_ALIAS_PREFIX,
+             cpstate->default_alias_num);
+
+    /* increment the default alias number */
+    cpstate->default_alias_num++;
+
+    return alias_name;
+}
diff --git a/src/backend/parser/cypher_transform_entity.c b/src/backend/parser/cypher_transform_entity.c
new file mode 100644
index 0000000..c9f13a8
--- /dev/null
+++ b/src/backend/parser/cypher_transform_entity.c
@@ -0,0 +1,149 @@
+/*
+ * 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.
+ */
+
+#include "postgres.h"
+
+#include "parser/cypher_transform_entity.h"
+
+// creates a transform entity
+transform_entity *make_transform_entity(cypher_parsestate *cpstate,
+                                        enum transform_entity_type type,
+                                        Node *node, Expr *expr)
+{
+    transform_entity *entity;
+
+    entity = palloc(sizeof(transform_entity));
+
+    entity->type = type;
+    if (type == ENT_VERTEX)
+    {
+        entity->entity.node = (cypher_node *)node;
+    }
+    else if (entity->type == ENT_EDGE || entity->type == ENT_VLE_EDGE)
+    {
+        entity->entity.rel = (cypher_relationship *)node;
+    }
+    else
+    {
+        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                        errmsg("unknown entity type")));
+    }
+
+    entity->declared_in_current_clause = true;
+    entity->expr = expr;
+    entity->in_join_tree = expr != NULL;
+
+    return entity;
+}
+
+/*
+ * Finds the transform_entity in the cypher_parstate for a the given name and
+ * type.
+ */
+transform_entity *find_transform_entity(cypher_parsestate *cpstate,
+                                        char *name,
+                                        enum transform_entity_type type)
+{
+    ListCell *lc;
+
+    foreach(lc, cpstate->entities)
+    {
+        transform_entity *entity = lfirst(lc);
+
+        if (entity->type != type)
+        {
+            continue;
+        }
+
+        if (type == ENT_VERTEX)
+        {
+            if (!strcmp(entity->entity.node->name, name))
+            {
+                return entity;
+            }
+        }
+        else if (type == ENT_EDGE || type == ENT_VLE_EDGE)
+        {
+            if (!strcmp(entity->entity.rel->name, name))
+            {
+                return entity;
+            }
+        }
+    }
+
+    return NULL;
+}
+
+/*
+ * Iterate through the cypher_parsestate's transform_entities and returns
+ * the entity with name passed by name variable.
+ */
+transform_entity *find_variable(cypher_parsestate *cpstate, char *name)
+{
+    ListCell *lc;
+
+    foreach (lc, cpstate->entities)
+    {
+        transform_entity *entity = lfirst(lc);
+        char *entity_name;
+
+        if (entity->type == ENT_VERTEX)
+        {
+            entity_name = entity->entity.node->name;
+        }
+        else if (entity->type == ENT_EDGE || entity->type == ENT_VLE_EDGE)
+        {
+            entity_name = entity->entity.rel->name;
+        }
+        else
+        {
+            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                            errmsg("unknown entity type")));
+        }
+
+        if (entity_name != NULL && !strcmp(name, entity_name))
+        {
+            return entity;
+        }
+    }
+
+    return NULL;
+}
+
+// helper function that extracts the name associated with the transform_entity.
+char *get_entity_name(transform_entity *entity)
+{
+    if (entity->type == ENT_EDGE || entity->type == ENT_VLE_EDGE)
+    {
+        return entity->entity.rel->name;
+    }
+    else if (entity->type == ENT_VERTEX)
+    {
+        return entity->entity.node->name;
+    }
+    else
+    {
+        ereport(ERROR,
+                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                 errmsg("cannot get entity name from transform_entity type %i",
+                        entity->type)));
+    }
+
+    return NULL;
+}
diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c
index a44d250..aac04f4 100644
--- a/src/backend/utils/adt/agtype.c
+++ b/src/backend/utils/adt/agtype.c
@@ -3147,7 +3147,7 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS)
     nargs = extract_variadic_args_min(fcinfo, 0, true, &args, &types, &nulls,
                                       2);
     /* return NULL if we don't have the minimum number of args */
-    if (args == NULL && nargs == 0)
+    if (args == NULL || nargs == 0 || nulls[0] == true)
     {
         PG_RETURN_NULL();
     }
diff --git a/src/include/parser/cypher_parse_node.h b/src/include/parser/cypher_parse_node.h
index f9da121..5b6cc69 100644
--- a/src/include/parser/cypher_parse_node.h
+++ b/src/include/parser/cypher_parse_node.h
@@ -23,6 +23,8 @@
 #include "nodes/primnodes.h"
 #include "parser/parse_node.h"
 
+#include "nodes/cypher_nodes.h"
+
 #define AGE_DEFAULT_ALIAS_PREFIX "_age_default_alias_"
 #define AGE_DEFAULT_VARNAME_PREFIX "_age_varname_"
 
@@ -61,5 +63,6 @@ void setup_errpos_ecb(errpos_ecb_state *ecb_state, ParseState *pstate,
                       int query_loc);
 void cancel_errpos_ecb(errpos_ecb_state *ecb_state);
 RangeTblEntry *find_rte(cypher_parsestate *cpstate, char *varname);
+char *get_next_default_alias(cypher_parsestate *cpstate);
 
 #endif
diff --git a/src/include/parser/cypher_transform_entity.h b/src/include/parser/cypher_transform_entity.h
new file mode 100644
index 0000000..3c02406
--- /dev/null
+++ b/src/include/parser/cypher_transform_entity.h
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+
+#ifndef AG_CYPHER_TRANSFORM_ENTITY_H
+#define AG_CYPHER_TRANSFORM_ENTITY_H
+
+#include "nodes/primnodes.h"
+#include "parser/parse_node.h"
+
+#include "nodes/cypher_nodes.h"
+#include "parser/cypher_parse_node.h"
+
+enum transform_entity_type
+{
+    ENT_VERTEX = 0x0,
+    ENT_EDGE,
+    ENT_VLE_EDGE
+};
+
+enum transform_entity_join_side
+{
+    JOIN_SIDE_LEFT = 0x0,
+    JOIN_SIDE_RIGHT
+};
+
+/*
+ * In the transformation stage, we need to track
+ * where a variable came from. When moving between
+ * clauses, Postgres parsestate and Query data structures
+ * are insufficient for some of the information we
+ * need.
+ */
+typedef struct
+{
+    // denotes whether this entity is a vertex or edge
+    enum transform_entity_type type;
+
+    /*
+     * MATCH clauses are transformed into a select * FROM ... JOIN, etc
+     * We need to know wheter the table that this entity represents is
+     * part of the join tree. If a cypher_node does not meet the conditions
+     * set in INCLUDE_NODE_IN_JOIN_TREE. Then we can skip the node when
+     * constructing our join tree. The entities around this particular entity
+     * need to know this for the join to get properly constructed.
+     */
+    bool in_join_tree;
+
+    /*
+     * The parse data structure will be transformed into an Expr that represents
+     * the entity. When contructing the join tree, we need to know what it was
+     * turned into. If the entity was originally created in a previous clause,
+     * this will be a Var that we need to reference to extract the id, startid,
+     * endid for the join. If the entity was created in the current clause, then
+     * this will be a FuncExpr that we can reference to get the id, startid, and
+     * endid.
+     */
+    Expr *expr;
+
+    /*
+     * tells each clause whether this variable was
+     * declared by itself or a previous clause.
+     */
+    bool declared_in_current_clause;
+    // The parse data structure that we transformed
+    union
+    {
+        cypher_node *node;
+        cypher_relationship *rel;
+    } entity;
+} transform_entity;
+
+transform_entity *find_variable(cypher_parsestate *cpstate, char *name);
+transform_entity *find_transform_entity(cypher_parsestate *cpstate,
+                                        char *name,
+                                        enum transform_entity_type type);
+transform_entity *make_transform_entity(cypher_parsestate *cpstate,
+                                        enum transform_entity_type type,
+                                        Node *node, Expr *expr);
+char *get_entity_name(transform_entity *entity);
+
+#endif