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