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 2020/09/04 00:39:20 UTC

[incubator-age] branch master updated: Add Property Constraints

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 c005f0a  Add Property Constraints
c005f0a is described below

commit c005f0a0a7ccd9e271de6948fa94d8fb96fae4dd
Author: Josh Innis <jo...@gmail.com>
AuthorDate: Wed Sep 2 13:20:56 2020 -0700

    Add Property Constraints
    
    Property Constraints now work with the MATCH and EXIST clauses
    
    Modified a CREATE clause unit test, so it will not need to
    be changed constantly
---
 age--0.2.0.sql                         |   7 ++
 regress/expected/cypher_create.out     |  22 ++---
 regress/expected/cypher_match.out      | 117 +++++++++++++++++++++++++--
 regress/sql/cypher_create.sql          |   2 +-
 regress/sql/cypher_match.sql           |  62 ++++++++++++++
 src/backend/parser/cypher_clause.c     | 143 ++++++++++++++++++++++-----------
 src/backend/parser/cypher_expr.c       |  15 ++++
 src/backend/parser/cypher_gram.y       |  11 +++
 src/backend/parser/cypher_parse_node.c |  19 +++++
 src/backend/utils/adt/agtype.c         |  20 +++++
 src/include/parser/cypher_parse_node.h |   2 +
 11 files changed, 357 insertions(+), 63 deletions(-)

diff --git a/age--0.2.0.sql b/age--0.2.0.sql
index 7b74f75..98ab451 100644
--- a/age--0.2.0.sql
+++ b/age--0.2.0.sql
@@ -910,6 +910,13 @@ STABLE
 PARALLEL SAFE
 AS 'MODULE_PATHNAME';
 
+CREATE FUNCTION _property_constraint_check(agtype, agtype)
+RETURNS boolean
+LANGUAGE c
+STABLE
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
 --
 -- String functions
 --
diff --git a/regress/expected/cypher_create.out b/regress/expected/cypher_create.out
index 1db5da4..ae54f3c 100644
--- a/regress/expected/cypher_create.out
+++ b/regress/expected/cypher_create.out
@@ -372,17 +372,17 @@ SELECT * FROM cypher_create.e_var;
 (30 rows)
 
 --Check every label has been created
-SELECT * FROM ag_label;
-       name       | graph | id | kind |            relation            
-------------------+-------+----+------+--------------------------------
- _ag_label_vertex | 17045 |  1 | v    | cypher_create._ag_label_vertex
- _ag_label_edge   | 17045 |  2 | e    | cypher_create._ag_label_edge
- v                | 17045 |  3 | v    | cypher_create.v
- e                | 17045 |  4 | e    | cypher_create.e
- n_var            | 17045 |  5 | v    | cypher_create.n_var
- e_var            | 17045 |  6 | e    | cypher_create.e_var
- n_other_node     | 17045 |  7 | v    | cypher_create.n_other_node
- b_var            | 17045 |  8 | e    | cypher_create.b_var
+SELECT name, kind FROM ag_label ORDER BY name;
+       name       | kind 
+------------------+------
+ _ag_label_edge   | e
+ _ag_label_vertex | v
+ b_var            | e
+ e                | e
+ e_var            | e
+ n_other_node     | v
+ n_var            | v
+ v                | v
 (8 rows)
 
 --Validate every vertex has the correct label
diff --git a/regress/expected/cypher_match.out b/regress/expected/cypher_match.out
index e3e2622..e7587d1 100644
--- a/regress/expected/cypher_match.out
+++ b/regress/expected/cypher_match.out
@@ -407,6 +407,82 @@ $$) AS (i agtype, b agtype, c agtype);
  1 | "initial" | "middle"
 (12 rows)
 
+--
+-- Property constraints
+--
+SELECT * FROM cypher('cypher_match',
+ $$CREATE ({string_key: "test", int_key: 1, float_key: 3.14, map_key: {key: "value"}, list_key: [1, 2, 3]}) $$)
+AS (p agtype);
+ p 
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_match',
+ $$CREATE ({lst: [1, NULL, 3.14, "string", {key: "value"}, []]}) $$)
+AS (p agtype);
+ p 
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (n  {string_key: NULL}) RETURN n $$)
+AS (n agtype);
+ n 
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (n  {string_key: "wrong value"}) RETURN n $$)
+AS (n agtype);
+ n 
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (n {string_key: "test", int_key: 1, float_key: 3.14, map_key: {key: "value"}, list_key: [1, 2, 3]})
+    RETURN n $$)
+AS (p agtype);
+                                                                                    p                                                                                    
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {"id": 281474976710657, "label": "", "properties": {"int_key": 1, "map_key": {"key": "value"}, "list_key": [1, 2, 3], "float_key": 3.14, "string_key": "test"}}::vertex
+(1 row)
+
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (n {string_key: "test"}) RETURN n $$)
+AS (p agtype);
+                                                                                    p                                                                                    
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {"id": 281474976710657, "label": "", "properties": {"int_key": 1, "map_key": {"key": "value"}, "list_key": [1, 2, 3], "float_key": 3.14, "string_key": "test"}}::vertex
+(1 row)
+
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (n {lst: [1, NULL, 3.14, "string", {key: "value"}, []]})  RETURN n $$)
+AS (p agtype);
+                                                          p                                                           
+----------------------------------------------------------------------------------------------------------------------
+ {"id": 281474976710658, "label": "", "properties": {"lst": [1, null, 3.14, "string", {"key": "value"}, []]}}::vertex
+(1 row)
+
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (n {lst: [1, NULL, 3.14, "string", {key: "value"}, [], "extra value"]})  RETURN n $$)
+AS (p agtype);
+ p 
+---
+(0 rows)
+
+--
+-- Prepared Statement Property Constraint
+--
+PREPARE property_ps(agtype) AS SELECT * FROM cypher('cypher_match',
+ $$MATCH (n $props) RETURN n $$, $1)
+AS (p agtype);
+EXECUTE property_ps(agtype_build_map('props',
+                                     agtype_build_map('string_key', 'test')));
+                                                                                    p                                                                                    
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {"id": 281474976710657, "label": "", "properties": {"int_key": 1, "map_key": {"key": "value"}, "list_key": [1, 2, 3], "float_key": 3.14, "string_key": "test"}}::vertex
+(1 row)
+
 -- need a following RETURN clause (should fail)
 SELECT * FROM cypher('cypher_match', $$MATCH (n:v)$$) AS (a agtype);
 ERROR:  syntax error at end of input
@@ -486,6 +562,31 @@ AS (u agtype, e agtype, v agtype);
  {"id": 1125899906842625, "label": "v1", "properties": {"id": "initial"}}::vertex | {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge | {"id": 1125899906842626, "label": "v1", "properties": {"id": "middle"}}::vertex
 (6 rows)
 
+-- Property Constraint in EXISTS
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (u) WHERE EXISTS((u)-[]->({id: "middle"})) RETURN u $$)
+AS (u agtype);
+                                        u                                         
+----------------------------------------------------------------------------------
+ {"id": 2251799813685251, "label": "v3", "properties": {"id": "end"}}::vertex
+ {"id": 2251799813685249, "label": "v3", "properties": {"id": "initial"}}::vertex
+ {"id": 1125899906842625, "label": "v1", "properties": {"id": "initial"}}::vertex
+(3 rows)
+
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (u) WHERE EXISTS((u)-[]->({id: "not a valid id"})) RETURN u $$)
+AS (u agtype);
+ u 
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (u) WHERE EXISTS((u)-[]->({id: NULL})) RETURN u $$)
+AS (u agtype);
+ u 
+---
+(0 rows)
+
 -- Exists checks for a loop. There shouldn't be any.
 SELECT * FROM cypher('cypher_match',
  $$MATCH (u)-[e]->(v) WHERE EXISTS((u)-[e]->(u)) RETURN u, e, v $$)
@@ -587,8 +688,10 @@ LINE 2:  $$MATCH (u)-[e]->(v) WHERE EXISTS((u)-[e]->(x)) RETURN u, e...
 --
 -- dump all vertices
 SELECT * FROM cypher('cypher_match', $$MATCH (u) RETURN u $$) AS (u agtype);
-                                         u                                          
-------------------------------------------------------------------------------------
+                                                                                    u                                                                                    
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {"id": 281474976710657, "label": "", "properties": {"int_key": 1, "map_key": {"key": "value"}, "list_key": [1, 2, 3], "float_key": 3.14, "string_key": "test"}}::vertex
+ {"id": 281474976710658, "label": "", "properties": {"lst": [1, null, 3.14, "string", {"key": "value"}, []]}}::vertex
  {"id": 844424930131969, "label": "v", "properties": {}}::vertex
  {"id": 844424930131970, "label": "v", "properties": {"i": 0}}::vertex
  {"id": 844424930131971, "label": "v", "properties": {"i": 1}}::vertex
@@ -602,7 +705,7 @@ SELECT * FROM cypher('cypher_match', $$MATCH (u) RETURN u $$) AS (u agtype);
  {"id": 2251799813685250, "label": "v3", "properties": {"id": "middle"}}::vertex
  {"id": 2251799813685251, "label": "v3", "properties": {"id": "end"}}::vertex
  {"id": 2814749767106561, "label": "loop", "properties": {"id": "initial"}}::vertex
-(13 rows)
+(15 rows)
 
 -- select vertices with id as a property
 SELECT * FROM cypher('cypher_match',
@@ -626,12 +729,14 @@ AS (u agtype);
 SELECT * FROM cypher('cypher_match',
  $$MATCH (u) WHERE NOT EXISTS(u.id) RETURN u $$)
 AS (u agtype);
-                                   u                                   
------------------------------------------------------------------------
+                                                                                    u                                                                                    
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {"id": 281474976710657, "label": "", "properties": {"int_key": 1, "map_key": {"key": "value"}, "list_key": [1, 2, 3], "float_key": 3.14, "string_key": "test"}}::vertex
+ {"id": 281474976710658, "label": "", "properties": {"lst": [1, null, 3.14, "string", {"key": "value"}, []]}}::vertex
  {"id": 844424930131969, "label": "v", "properties": {}}::vertex
  {"id": 844424930131970, "label": "v", "properties": {"i": 0}}::vertex
  {"id": 844424930131971, "label": "v", "properties": {"i": 1}}::vertex
-(3 rows)
+(5 rows)
 
 -- select vertices without id as a property but with a property i
 SELECT * FROM cypher('cypher_match',
diff --git a/regress/sql/cypher_create.sql b/regress/sql/cypher_create.sql
index bf44838..2ef9b80 100644
--- a/regress/sql/cypher_create.sql
+++ b/regress/sql/cypher_create.sql
@@ -177,7 +177,7 @@ SELECT * FROM cypher_create.n_var;
 SELECT * FROM cypher_create.e_var;
 
 --Check every label has been created
-SELECT * FROM ag_label;
+SELECT name, kind FROM ag_label ORDER BY name;
 
 --Validate every vertex has the correct label
 SELECT * FROM cypher('cypher_create', $$MATCH (n) RETURN n$$) AS (n agtype);
diff --git a/regress/sql/cypher_match.sql b/regress/sql/cypher_match.sql
index 6d8044a..82da895 100644
--- a/regress/sql/cypher_match.sql
+++ b/regress/sql/cypher_match.sql
@@ -213,6 +213,54 @@ SELECT * FROM cypher('cypher_match', $$
 	RETURN a.i, b.id, c.id
 $$) AS (i agtype, b agtype, c agtype);
 
+--
+-- Property constraints
+--
+SELECT * FROM cypher('cypher_match',
+ $$CREATE ({string_key: "test", int_key: 1, float_key: 3.14, map_key: {key: "value"}, list_key: [1, 2, 3]}) $$)
+AS (p agtype);
+
+SELECT * FROM cypher('cypher_match',
+ $$CREATE ({lst: [1, NULL, 3.14, "string", {key: "value"}, []]}) $$)
+AS (p agtype);
+
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (n  {string_key: NULL}) RETURN n $$)
+AS (n agtype);
+
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (n  {string_key: "wrong value"}) RETURN n $$)
+AS (n agtype);
+
+
+SELECT * FROM cypher('cypher_match', $$
+    MATCH (n {string_key: "test", int_key: 1, float_key: 3.14, map_key: {key: "value"}, list_key: [1, 2, 3]})
+    RETURN n $$)
+AS (p agtype);
+
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (n {string_key: "test"}) RETURN n $$)
+AS (p agtype);
+
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (n {lst: [1, NULL, 3.14, "string", {key: "value"}, []]})  RETURN n $$)
+AS (p agtype);
+
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (n {lst: [1, NULL, 3.14, "string", {key: "value"}, [], "extra value"]})  RETURN n $$)
+AS (p agtype);
+
+
+--
+-- Prepared Statement Property Constraint
+--
+PREPARE property_ps(agtype) AS SELECT * FROM cypher('cypher_match',
+ $$MATCH (n $props) RETURN n $$, $1)
+AS (p agtype);
+
+EXECUTE property_ps(agtype_build_map('props',
+                                     agtype_build_map('string_key', 'test')));
+
 -- need a following RETURN clause (should fail)
 SELECT * FROM cypher('cypher_match', $$MATCH (n:v)$$) AS (a agtype);
 
@@ -255,6 +303,20 @@ SELECT * FROM cypher('cypher_match',
  $$MATCH (u)-[e]->(v) WHERE EXISTS((u)-[e]->(v)) RETURN u, e, v $$)
 AS (u agtype, e agtype, v agtype);
 
+
+-- Property Constraint in EXISTS
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (u) WHERE EXISTS((u)-[]->({id: "middle"})) RETURN u $$)
+AS (u agtype);
+
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (u) WHERE EXISTS((u)-[]->({id: "not a valid id"})) RETURN u $$)
+AS (u agtype);
+
+SELECT * FROM cypher('cypher_match',
+ $$MATCH (u) WHERE EXISTS((u)-[]->({id: NULL})) RETURN u $$)
+AS (u agtype);
+
 -- Exists checks for a loop. There shouldn't be any.
 SELECT * FROM cypher('cypher_match',
  $$MATCH (u)-[e]->(v) WHERE EXISTS((u)-[e]->(u)) RETURN u, e, v $$)
diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c
index 8484099..f73a5e4 100644
--- a/src/backend/parser/cypher_clause.c
+++ b/src/backend/parser/cypher_clause.c
@@ -158,6 +158,9 @@ make_transform_entity(cypher_parsestate *cpstate,
                       enum transform_entity_type type, Node *node, Expr *expr,
                       cypher_target_node *target_node);
 static transform_entity *find_variable(cypher_parsestate *cpstate, char *name);
+static Node *create_property_constraint_function(cypher_parsestate *cpstate,
+                                                 transform_entity *entity,
+                                                 Node *property_constraints);
 static TargetEntry *findTarget(List *targetList, char *resname);
 // create clause
 static Query *transform_cypher_create(cypher_parsestate *cpstate,
@@ -195,7 +198,6 @@ static bool variable_exists(cypher_parsestate *cpstate, char *name);
 static int get_target_entry_resno(List *target_list, char *name);
 static TargetEntry *placeholder_target_entry(cypher_parsestate *cpstate,
                                              char *name);
-static RangeTblEntry *find_prev_cypher_clause(cypher_parsestate *cpstate);
 static Query *transform_cypher_sub_pattern(cypher_parsestate *cpstate,
                                            cypher_clause *clause);
 
@@ -606,7 +608,7 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query,
     ListCell *lc;
     List *quals = NIL;
     Expr *q = NULL;
-    Node *expr = NULL;
+    Expr *expr = NULL;
 
     foreach (lc, pattern)
     {
@@ -619,11 +621,22 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query,
     if (quals != NIL)
     {
         q = makeBoolExpr(AND_EXPR, quals, -1);
-        expr = transformExpr(&cpstate->pstate, (Node *)q, EXPR_KIND_WHERE);
+        expr = (Expr *)transformExpr(&cpstate->pstate, (Node *)q, EXPR_KIND_WHERE);
     }
 
+    if (cpstate->property_constraint_quals != NIL)
+    {
+        Expr *prop_qual = makeBoolExpr(AND_EXPR, cpstate->property_constraint_quals, -1);
+
+        if (quals == NIL)
+            expr = prop_qual;
+        else
+            expr = makeBoolExpr(AND_EXPR, list_make2(expr, prop_qual), -1);
+    }
+
+
     query->rtable = cpstate->pstate.p_rtable;
-    query->jointree = makeFromExpr(cpstate->pstate.p_joinlist, expr);
+    query->jointree = makeFromExpr(cpstate->pstate.p_joinlist, (Node *)expr);
 }
 
 static char *get_next_default_alias(cypher_parsestate *cpstate)
@@ -937,8 +950,7 @@ static List *make_edge_quals(cypher_parsestate *cpstate,
     default:
         ereport(ERROR,
                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                 errmsg("edges with a property condition are not supported"),
-                 parser_errposition(pstate, edge->entity.rel->location)));
+                 errmsg("Unknown relationship direction")));
     }
     return NIL;
 }
@@ -1022,6 +1034,54 @@ static transform_entity *find_variable(cypher_parsestate *cpstate, char *name)
 }
 
 /*
+ * Create a function to handle property constraints on an edge/vertex.
+ * Since the property constraints might be a parameter, we cannot split
+ * the property map into indvidual quals, this will be slightly inefficient,
+ * but necessary to cover all possible situations.
+ */
+static Node *create_property_constraint_function(cypher_parsestate *cpstate,
+                                                 transform_entity *entity,
+                                                 Node *property_constraints)
+{
+    ParseState *pstate = (ParseState *)cpstate;
+    char *entity_name;
+    ColumnRef *cr;
+    FuncExpr *fexpr;
+    Oid func_oid;
+    Node *prop_expr, *const_expr;
+    RangeTblEntry *rte;
+
+    cr = makeNode(ColumnRef);
+
+    if (entity->type == ENT_EDGE)
+        entity_name = entity->entity.node->name;
+    else if (entity->type == ENT_VERTEX)
+        entity_name = entity->entity.rel->name;
+
+    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 cypher to get the constraints' transform node
+    const_expr = transform_cypher_expr(cpstate, property_constraints,
+                                       EXPR_KIND_WHERE);
+
+    func_oid = get_ag_func_oid("_property_constraint_check", 2, AGTYPEOID,
+                               AGTYPEOID);
+
+    fexpr = makeFuncExpr(func_oid, BOOLOID, list_make2(prop_expr, const_expr),
+                         InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL);
+
+    return (Node *)fexpr;
+}
+
+
+/*
  * For the given path, transform each entity within the path, create
  * the path variable if needed, and construct the quals to enforce the
  * correct join tree, and enforce edge uniqueness.
@@ -1091,6 +1151,12 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query,
             entity = make_transform_entity(cpstate, ENT_VERTEX, (Node *)node,
                                            expr, NULL);
 
+            if (node->props)
+            {
+                Node *n = create_property_constraint_function(cpstate, entity, node->props);
+                cpstate->property_constraint_quals = lappend(cpstate->property_constraint_quals, n);
+            }
+
             entities = lappend(entities, entity);
         }
         else
@@ -1102,6 +1168,12 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query,
             entity = make_transform_entity(cpstate, ENT_EDGE, (Node *)rel,
                                            expr, NULL);
 
+            if (rel->props)
+            {
+                Node *n = create_property_constraint_function(cpstate, entity, rel->props);
+                cpstate->property_constraint_quals = lappend(cpstate->property_constraint_quals, n);
+            }
+
             entities = lappend(entities, entity);
         }
 
@@ -1236,6 +1308,10 @@ static char *get_accessor_function_name(enum transform_entity_type type,
         // id
         if (!strcmp(AG_VERTEX_COLNAME_ID, name))
             return AG_VERTEX_ACCESS_FUNCTION_ID;
+        // props
+        else if(!strcmp(AG_VERTEX_COLNAME_PROPERTIES, name))
+            return AG_VERTEX_ACCESS_FUNCTION_PROPERTIES;
+
     }
     if (type == ENT_EDGE)
     {
@@ -1248,6 +1324,9 @@ static char *get_accessor_function_name(enum transform_entity_type type,
         // end id
         else if (!strcmp(AG_EDGE_COLNAME_END_ID, name))
             return AG_EDGE_ACCESS_FUNCTION_END_ID;
+        // props
+        else if(!strcmp(AG_VERTEX_COLNAME_PROPERTIES, name))
+            return AG_VERTEX_ACCESS_FUNCTION_PROPERTIES;
     }
 
     ereport(ERROR,
@@ -1382,14 +1461,6 @@ static Expr *transform_cypher_edge(cypher_parsestate *cpstate,
                      parser_errposition(pstate, rel->location)));
     }
 
-    if (rel->props)
-    {
-        ereport(ERROR,
-                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                 errmsg("edges with a property condition are not supported"),
-                 parser_errposition(pstate, rel->location)));
-    }
-
     if (!rel->name)
         rel->name = get_next_default_alias(cpstate);
 
@@ -1501,15 +1572,6 @@ static Expr *transform_cypher_node(cypher_parsestate *cpstate,
                      parser_errposition(pstate, node->location)));
     }
 
-    // NOTE: for now, nodes with a property condition are not supported
-    if (node->props)
-    {
-        ereport(ERROR,
-                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                 errmsg("nodes with a property condition are not supported"),
-                 parser_errposition(pstate, node->location)));
-    }
-
     if (!node->name)
         node->name = get_next_default_alias(cpstate);
 
@@ -1931,7 +1993,7 @@ static bool variable_exists(cypher_parsestate *cpstate, char *name)
     if (name == NULL)
         return false;
 
-    rte = find_prev_cypher_clause(cpstate);
+    rte = find_rte(cpstate, PREV_CYPHER_CLAUSE_ALIAS);
     if (rte)
     {
         id = scanRTEForColumn(pstate, rte, name, -1, 0, NULL);
@@ -2021,7 +2083,7 @@ static cypher_target_node *transform_create_cypher_existing_node(
     cypher_target_node *rel = palloc(sizeof(cypher_target_node));
     char *alias;
     int resno;
-    RangeTblEntry *rte = find_prev_cypher_clause(cpstate);
+    RangeTblEntry *rte = find_rte(cpstate, PREV_CYPHER_CLAUSE_ALIAS);
     TargetEntry *te;
     Expr *result;
 
@@ -2215,6 +2277,16 @@ static Expr *cypher_create_properties(cypher_parsestate *cpstate,
 {
     Expr *properties;
 
+    if (props != NULL && is_ag_node(props, cypher_param))
+    {
+        ParseState *pstate = (ParseState *) cpstate;
+        cypher_param *param = (cypher_param *)props;
+
+        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                errmsg("properties in a CREATE clause as a parameter is not supported"),
+                parser_errposition(pstate, param->location)));
+    }
+
     if (type != ENT_VERTEX && type != ENT_EDGE)
         ereport(ERROR, (errmsg_internal("unreconized entity type")));
 
@@ -2228,7 +2300,7 @@ static Expr *cypher_create_properties(cypher_parsestate *cpstate,
         properties = (Expr *)build_column_default(
             label_relation, Anum_ag_label_edge_table_properties);
 
-    // add a volatile wrapper call to prevent the optimizer from removing
+    // add a volatile wrapper call to prevent the optimizer from removing it
     return (Expr *)add_volatile_wrapper(properties);
 }
 
@@ -2249,25 +2321,6 @@ static Node *cypher_create_id_default(cypher_parsestate *cpstate,
     return id;
 }
 
-static RangeTblEntry *find_prev_cypher_clause(cypher_parsestate *cpstate)
-{
-    ParseState *pstate = (ParseState *)cpstate;
-    ListCell *lc;
-
-    foreach (lc, pstate->p_rtable)
-    {
-        RangeTblEntry *rte = (RangeTblEntry *)lfirst(lc);
-        Alias *alias = rte->alias;
-        if (!alias)
-            continue;
-
-        if (!strcmp(alias->aliasname, PREV_CYPHER_CLAUSE_ALIAS))
-            return rte;
-    }
-
-    return NULL;
-}
-
 /*
  * makeNamespaceItem (from PG makeNamespaceItem)-
  *        Convenience subroutine to construct a ParseNamespaceItem.
diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c
index 0af5d5d..d313d02 100644
--- a/src/backend/parser/cypher_expr.c
+++ b/src/backend/parser/cypher_expr.c
@@ -25,6 +25,7 @@
 #include "nodes/value.h"
 #include "optimizer/tlist.h"
 #include "parser/parse_coerce.h"
+#include "parser/parse_expr.h"
 #include "parser/cypher_clause.h"
 #include "parser/parse_node.h"
 #include "parser/parse_oper.h"
@@ -34,6 +35,7 @@
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
+#include "commands/label_commands.h"
 #include "nodes/cypher_nodes.h"
 #include "parser/cypher_expr.h"
 #include "parser/cypher_parse_node.h"
@@ -310,6 +312,19 @@ static Node *transform_ColumnRef(cypher_parsestate *cpstate, ColumnRef *cref)
     var = colNameToVar(pstate, colname, false, cref->location);
     if (!var)
     {
+        RangeTblEntry *rte;
+
+        /*
+         * If we find an rte with the column ref name, this expr might be
+         * referencing a property in a vertex or edge. In that case switch
+         * the columnRef to a ColumnRef of the rte.
+         */
+        if ((rte = find_rte(cpstate, (char *)colname)))
+        {
+            return scanRTEForColumn(pstate, rte, AG_VERTEX_COLNAME_PROPERTIES,
+                                    -1, 0, NULL);
+        }
+
         ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN),
                         errmsg("variable `%s` does not exist", colname),
                         parser_errposition(pstate, cref->location)));
diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y
index 919b445..afd76ac 100644
--- a/src/backend/parser/cypher_gram.y
+++ b/src/backend/parser/cypher_gram.y
@@ -795,6 +795,17 @@ properties_opt:
             $$ = NULL;
         }
     | map
+    | PARAMETER
+        {
+            cypher_param *n;
+
+            n = make_ag_node(cypher_param);
+            n->name = $1;
+            n->location = @1;
+
+            $$ = (Node *)n;
+        }
+
     ;
 
 /*
diff --git a/src/backend/parser/cypher_parse_node.c b/src/backend/parser/cypher_parse_node.c
index efdca8f..37a41cb 100644
--- a/src/backend/parser/cypher_parse_node.c
+++ b/src/backend/parser/cypher_parse_node.c
@@ -97,3 +97,22 @@ static void errpos_ecb(void *arg)
                                      ecb_state->query_loc);
     errposition(query_pos + geterrposition());
 }
+
+RangeTblEntry *find_rte(cypher_parsestate *cpstate, char *varname)
+{
+    ParseState *pstate = (ParseState *) cpstate;
+    ListCell *lc;
+
+    foreach (lc, pstate->p_rtable)
+    {
+        RangeTblEntry *rte = (RangeTblEntry *)lfirst(lc);
+        Alias *alias = rte->alias;
+        if (!alias)
+            continue;
+
+        if (!strcmp(alias->aliasname, varname))
+            return rte;
+    }
+
+    return NULL;
+}
diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c
index 0cadda3..8ee02dd 100644
--- a/src/backend/utils/adt/agtype.c
+++ b/src/backend/utils/adt/agtype.c
@@ -3126,6 +3126,26 @@ static agtype_value *get_agtype_value_object_value(agtype_value *agtv_object,
     return NULL;
 }
 
+PG_FUNCTION_INFO_V1(_property_constraint_check);
+
+Datum _property_constraint_check(PG_FUNCTION_ARGS)
+{
+    agtype_iterator *constraint_it, *property_it;
+    agtype *properties, *constraints;
+
+    if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
+        PG_RETURN_BOOL(false);
+
+    properties = AG_GET_ARG_AGTYPE_P(0);
+    constraints = AG_GET_ARG_AGTYPE_P(1);
+
+    constraint_it = agtype_iterator_init(&constraints->root);
+    property_it = agtype_iterator_init(&properties->root);
+
+    PG_RETURN_BOOL(agtype_deep_contains(&property_it, &constraint_it));
+}
+
+
 PG_FUNCTION_INFO_V1(id);
 
 Datum id(PG_FUNCTION_ARGS)
diff --git a/src/include/parser/cypher_parse_node.h b/src/include/parser/cypher_parse_node.h
index 9fac2cb..d70a4ac 100644
--- a/src/include/parser/cypher_parse_node.h
+++ b/src/include/parser/cypher_parse_node.h
@@ -30,6 +30,7 @@ typedef struct cypher_parsestate
     Param *params;
     int default_alias_num;
     List *entities;
+    List *property_constraint_quals;
 } cypher_parsestate;
 
 typedef struct errpos_ecb_state
@@ -46,5 +47,6 @@ void free_cypher_parsestate(cypher_parsestate *cpstate);
 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);
 
 #endif