Skip to content

Commit 98c2bfa

Browse files
jrgemignaniZainab-Saad
authored andcommitted
Fix issue 1219 - MERGE not seeing previous clause var (apache#1441)
Fixed issue 1219 where MERGE did not see the previous clause's variable. This description is a bit misleading as the transform phase did see the variable and was able to use it. However, the planner phase removed the variable by replacing it with a NULL Const. This caused MERGE to see a NULL Const for the previous tuple, generating incorrect results. However, this only occurred for very specific cases. Fx: MATCH (x) MERGE (y {id: id(x)}) -- worked MATCH (x) MERGE (y {id: id(x)}) RETURN y -- didn't MATCH (x) MERGE (y {id: id(x)}) RETURN x, y -- worked The change impacted no current regression tests and involved wrapping all explicitly defined variables' target entries with a volatile wrapper. Added new regression tests.
1 parent ba64fbb commit 98c2bfa

File tree

8 files changed

+222
-10
lines changed

8 files changed

+222
-10
lines changed

regress/expected/cypher_merge.out

+110
Original file line numberDiff line numberDiff line change
@@ -1072,6 +1072,116 @@ SELECT * FROM cypher('cypher_merge', $$ MERGE p=(x:r)-[y:E]->(p)-[x]->(y) $$) AS
10721072
ERROR: variable "p" is for a path
10731073
LINE 1: ...M cypher('cypher_merge', $$ MERGE p=(x:r)-[y:E]->(p)-[x]->(y...
10741074
^
1075+
-- issue 1219
1076+
SELECT * FROM create_graph('issue_1219');
1077+
NOTICE: graph "issue_1219" has been created
1078+
create_graph
1079+
--------------
1080+
1081+
(1 row)
1082+
1083+
SELECT * FROM cypher('issue_1219', $$ CREATE (x:Label1{arr:[1,2,3,4]}) RETURN x $$) as (a agtype);
1084+
a
1085+
-----------------------------------------------------------------------------------------
1086+
{"id": 844424930131969, "label": "Label1", "properties": {"arr": [1, 2, 3, 4]}}::vertex
1087+
(1 row)
1088+
1089+
SELECT * FROM cypher('issue_1219', $$
1090+
MATCH (x:Label1{arr:[1,2,3,4]}) MERGE (y:Label2{key1:2, key2:x.arr, key3:3}) RETURN y
1091+
$$) as (result agtype);
1092+
result
1093+
-----------------------------------------------------------------------------------------------------------------
1094+
{"id": 1125899906842625, "label": "Label2", "properties": {"key1": 2, "key2": [1, 2, 3, 4], "key3": 3}}::vertex
1095+
(1 row)
1096+
1097+
SELECT * FROM cypher('issue_1219', $$
1098+
MATCH (x:Label1{arr:[1,2,3,4]}) MERGE (y:Label2{key2:id(x)}) RETURN y
1099+
$$) as (result agtype);
1100+
result
1101+
----------------------------------------------------------------------------------------------
1102+
{"id": 1125899906842626, "label": "Label2", "properties": {"key2": 844424930131969}}::vertex
1103+
(1 row)
1104+
1105+
SELECT * FROM cypher('issue_1219', $$ MATCH (x) RETURN x $$) as (a agtype);
1106+
a
1107+
-----------------------------------------------------------------------------------------------------------------
1108+
{"id": 844424930131969, "label": "Label1", "properties": {"arr": [1, 2, 3, 4]}}::vertex
1109+
{"id": 1125899906842625, "label": "Label2", "properties": {"key1": 2, "key2": [1, 2, 3, 4], "key3": 3}}::vertex
1110+
{"id": 1125899906842626, "label": "Label2", "properties": {"key2": 844424930131969}}::vertex
1111+
(3 rows)
1112+
1113+
-- these shouldn't create more
1114+
SELECT * FROM cypher('issue_1219', $$
1115+
MATCH (x:Label1{arr:[1,2,3,4]}) MERGE (y:Label2{key1:2, key2:x.arr, key3:3}) RETURN y
1116+
$$) as (result agtype);
1117+
result
1118+
-----------------------------------------------------------------------------------------------------------------
1119+
{"id": 1125899906842625, "label": "Label2", "properties": {"key1": 2, "key2": [1, 2, 3, 4], "key3": 3}}::vertex
1120+
(1 row)
1121+
1122+
SELECT * FROM cypher('issue_1219', $$
1123+
MATCH (x:Label1{arr:[1,2,3,4]}) MERGE (y:Label2{key2:id(x)}) RETURN y
1124+
$$) as (result agtype);
1125+
result
1126+
----------------------------------------------------------------------------------------------
1127+
{"id": 1125899906842626, "label": "Label2", "properties": {"key2": 844424930131969}}::vertex
1128+
(1 row)
1129+
1130+
SELECT * FROM cypher('issue_1219', $$ MATCH (x) RETURN x $$) as (a agtype);
1131+
a
1132+
-----------------------------------------------------------------------------------------------------------------
1133+
{"id": 844424930131969, "label": "Label1", "properties": {"arr": [1, 2, 3, 4]}}::vertex
1134+
{"id": 1125899906842625, "label": "Label2", "properties": {"key1": 2, "key2": [1, 2, 3, 4], "key3": 3}}::vertex
1135+
{"id": 1125899906842626, "label": "Label2", "properties": {"key2": 844424930131969}}::vertex
1136+
(3 rows)
1137+
1138+
-- create a path
1139+
SELECT * FROM cypher('issue_1219', $$
1140+
CREATE (u:Label1{name: "John"})-[e:knows]->(v:Label1{name: "Jane"})
1141+
$$) as (result agtype);
1142+
result
1143+
--------
1144+
(0 rows)
1145+
1146+
SELECT * FROM cypher('issue_1219', $$
1147+
MATCH (u:Label1{name:"John"})-[e:knows]->(v:Label1{name: "Jane"})
1148+
MERGE (y:Label2{start_id:id(u), edge_id:id(e), end_id:id(v)}) RETURN y
1149+
$$) as (result agtype);
1150+
result
1151+
----------------------------------------------------------------------------------------------------------------------------------------------------------
1152+
{"id": 1125899906842627, "label": "Label2", "properties": {"end_id": 844424930131971, "edge_id": 1407374883553281, "start_id": 844424930131970}}::vertex
1153+
(1 row)
1154+
1155+
SELECT * FROM cypher('issue_1219', $$ MATCH (x) RETURN x $$) as (result agtype);
1156+
result
1157+
----------------------------------------------------------------------------------------------------------------------------------------------------------
1158+
{"id": 844424930131969, "label": "Label1", "properties": {"arr": [1, 2, 3, 4]}}::vertex
1159+
{"id": 844424930131970, "label": "Label1", "properties": {"name": "John"}}::vertex
1160+
{"id": 844424930131971, "label": "Label1", "properties": {"name": "Jane"}}::vertex
1161+
{"id": 1125899906842625, "label": "Label2", "properties": {"key1": 2, "key2": [1, 2, 3, 4], "key3": 3}}::vertex
1162+
{"id": 1125899906842626, "label": "Label2", "properties": {"key2": 844424930131969}}::vertex
1163+
{"id": 1125899906842627, "label": "Label2", "properties": {"end_id": 844424930131971, "edge_id": 1407374883553281, "start_id": 844424930131970}}::vertex
1164+
(6 rows)
1165+
1166+
SELECT * FROM cypher('issue_1219', $$ MATCH ()-[e]->() RETURN e $$) as (result agtype);
1167+
result
1168+
----------------------------------------------------------------------------------------------------------------------------
1169+
{"id": 1407374883553281, "label": "knows", "end_id": 844424930131971, "start_id": 844424930131970, "properties": {}}::edge
1170+
(1 row)
1171+
1172+
SELECT drop_graph('issue_1219', true);
1173+
NOTICE: drop cascades to 5 other objects
1174+
DETAIL: drop cascades to table issue_1219._ag_label_vertex
1175+
drop cascades to table issue_1219._ag_label_edge
1176+
drop cascades to table issue_1219."Label1"
1177+
drop cascades to table issue_1219."Label2"
1178+
drop cascades to table issue_1219.knows
1179+
NOTICE: graph "issue_1219" has been dropped
1180+
drop_graph
1181+
------------
1182+
1183+
(1 row)
1184+
10751185
--clean up
10761186
SELECT * FROM cypher('cypher_merge', $$MATCH (n) DETACH DELETE n $$) AS (a agtype);
10771187
a

regress/sql/cypher_merge.sql

+30
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,36 @@ SELECT * FROM cypher('cypher_merge', $$ MERGE p=(x:r)-[y:E]->(x)-[p]->(x) $$) AS
524524
SELECT * FROM cypher('cypher_merge', $$ MERGE p=(x:r)-[y:E]->(x)-[p:E]->(x) $$) AS (x agtype);
525525
SELECT * FROM cypher('cypher_merge', $$ MERGE p=(x:r)-[y:E]->(p)-[x]->(y) $$) AS (x agtype);
526526

527+
-- issue 1219
528+
SELECT * FROM create_graph('issue_1219');
529+
SELECT * FROM cypher('issue_1219', $$ CREATE (x:Label1{arr:[1,2,3,4]}) RETURN x $$) as (a agtype);
530+
SELECT * FROM cypher('issue_1219', $$
531+
MATCH (x:Label1{arr:[1,2,3,4]}) MERGE (y:Label2{key1:2, key2:x.arr, key3:3}) RETURN y
532+
$$) as (result agtype);
533+
SELECT * FROM cypher('issue_1219', $$
534+
MATCH (x:Label1{arr:[1,2,3,4]}) MERGE (y:Label2{key2:id(x)}) RETURN y
535+
$$) as (result agtype);
536+
SELECT * FROM cypher('issue_1219', $$ MATCH (x) RETURN x $$) as (a agtype);
537+
-- these shouldn't create more
538+
SELECT * FROM cypher('issue_1219', $$
539+
MATCH (x:Label1{arr:[1,2,3,4]}) MERGE (y:Label2{key1:2, key2:x.arr, key3:3}) RETURN y
540+
$$) as (result agtype);
541+
SELECT * FROM cypher('issue_1219', $$
542+
MATCH (x:Label1{arr:[1,2,3,4]}) MERGE (y:Label2{key2:id(x)}) RETURN y
543+
$$) as (result agtype);
544+
SELECT * FROM cypher('issue_1219', $$ MATCH (x) RETURN x $$) as (a agtype);
545+
-- create a path
546+
SELECT * FROM cypher('issue_1219', $$
547+
CREATE (u:Label1{name: "John"})-[e:knows]->(v:Label1{name: "Jane"})
548+
$$) as (result agtype);
549+
SELECT * FROM cypher('issue_1219', $$
550+
MATCH (u:Label1{name:"John"})-[e:knows]->(v:Label1{name: "Jane"})
551+
MERGE (y:Label2{start_id:id(u), edge_id:id(e), end_id:id(v)}) RETURN y
552+
$$) as (result agtype);
553+
SELECT * FROM cypher('issue_1219', $$ MATCH (x) RETURN x $$) as (result agtype);
554+
SELECT * FROM cypher('issue_1219', $$ MATCH ()-[e]->() RETURN e $$) as (result agtype);
555+
SELECT drop_graph('issue_1219', true);
556+
527557
--clean up
528558
SELECT * FROM cypher('cypher_merge', $$MATCH (n) DETACH DELETE n $$) AS (a agtype);
529559

src/backend/nodes/cypher_outfuncs.c

+4
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ void out_cypher_path(StringInfo str, const ExtensibleNode *node)
194194
DEFINE_AG_NODE(cypher_path);
195195

196196
WRITE_NODE_FIELD(path);
197+
WRITE_STRING_FIELD(var_name);
198+
WRITE_STRING_FIELD(parsed_var_name);
197199
WRITE_LOCATION_FIELD(location);
198200
}
199201

@@ -203,6 +205,7 @@ void out_cypher_node(StringInfo str, const ExtensibleNode *node)
203205
DEFINE_AG_NODE(cypher_node);
204206

205207
WRITE_STRING_FIELD(name);
208+
WRITE_STRING_FIELD(parsed_name);
206209
WRITE_STRING_FIELD(label);
207210
WRITE_STRING_FIELD(parsed_label);
208211
WRITE_NODE_FIELD(props);
@@ -215,6 +218,7 @@ void out_cypher_relationship(StringInfo str, const ExtensibleNode *node)
215218
DEFINE_AG_NODE(cypher_relationship);
216219

217220
WRITE_STRING_FIELD(name);
221+
WRITE_STRING_FIELD(parsed_name);
218222
WRITE_STRING_FIELD(label);
219223
WRITE_STRING_FIELD(parsed_label);
220224
WRITE_NODE_FIELD(props);

src/backend/optimizer/cypher_createplan.c

+5-5
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ Plan *plan_cypher_set_path(PlannerInfo *root, RelOptInfo *rel,
122122
}
123123

124124
/*
125-
* Coverts the Scan node representing the delete clause
125+
* Coverts the Scan node representing the DELETE clause
126126
* to the delete Plan node
127127
*/
128128
Plan *plan_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel,
@@ -168,7 +168,7 @@ Plan *plan_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel,
168168
// child plan nodes are here, Postgres processed them for us.
169169
cs->custom_plans = custom_plans;
170170
cs->custom_exprs = NIL;
171-
// transfer delete metadata needed by the delete clause.
171+
// transfer delete metadata needed by the DELETE clause.
172172
cs->custom_private = best_path->custom_private;
173173
/*
174174
* the scan list of the delete node's children, used for ScanTupleSlot
@@ -183,7 +183,7 @@ Plan *plan_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel,
183183
}
184184

185185
/*
186-
* Coverts the Scan node representing the delete clause
186+
* Coverts the Scan node representing the MERGE clause
187187
* to the merge Plan node
188188
*/
189189
Plan *plan_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel,
@@ -206,7 +206,7 @@ Plan *plan_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel,
206206

207207
cs->scan.plan.plan_node_id = 0; // Set later in set_plan_refs
208208
/*
209-
* the scan list of the delete node, used for its ScanTupleSlot used
209+
* the scan list of the merge node, used for its ScanTupleSlot used
210210
* by its parent in the execution phase.
211211
*/
212212
cs->scan.plan.targetlist = tlist;
@@ -229,7 +229,7 @@ Plan *plan_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel,
229229
// child plan nodes are here, Postgres processed them for us.
230230
cs->custom_plans = custom_plans;
231231
cs->custom_exprs = NIL;
232-
// transfer delete metadata needed by the delete clause.
232+
// transfer delete metadata needed by the MERGE clause.
233233
cs->custom_private = best_path->custom_private;
234234
/*
235235
* the scan list of the merge node's children, used for ScanTupleSlot

src/backend/optimizer/cypher_pathnode.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ CustomPath *create_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel,
151151
}
152152

153153
/*
154-
* Creates a Delete Path. Makes the original path a child of the new
154+
* Creates a merge path. Makes the original path a child of the new
155155
* path. We leave it to the caller to replace the pathlist of the rel.
156156
*/
157157
CustomPath *create_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel,

src/backend/parser/cypher_clause.c

+64-4
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,8 @@ static Node *transform_clause_for_join(cypher_parsestate *cpstate,
279279
Alias* alias);
280280
static cypher_clause *convert_merge_to_match(cypher_merge *merge);
281281
static void
282-
transform_cypher_merge_mark_tuple_position(List *target_list,
282+
transform_cypher_merge_mark_tuple_position(cypher_parsestate *cpstate,
283+
List *target_list,
283284
cypher_create_path *path);
284285
static cypher_target_node *get_referenced_variable(ParseState *pstate,
285286
Node *node,
@@ -6262,7 +6263,7 @@ static Query *transform_cypher_merge(cypher_parsestate *cpstate,
62626263
* For the metadata need to create paths, find the tuple position that
62636264
* will represent the entity in the execution phase.
62646265
*/
6265-
transform_cypher_merge_mark_tuple_position(query->targetList,
6266+
transform_cypher_merge_mark_tuple_position(cpstate, query->targetList,
62666267
merge_path);
62676268
}
62686269

@@ -6413,7 +6414,7 @@ transform_merge_make_lateral_join(cypher_parsestate *cpstate, Query *query,
64136414
* For the metadata need to create paths, find the tuple position that
64146415
* will represent the entity in the execution phase.
64156416
*/
6416-
transform_cypher_merge_mark_tuple_position(query->targetList,
6417+
transform_cypher_merge_mark_tuple_position(cpstate, query->targetList,
64176418
merge_path);
64186419

64196420
return merge_path;
@@ -6425,7 +6426,8 @@ transform_merge_make_lateral_join(cypher_parsestate *cpstate, Query *query,
64256426
* function to keep the optimizer from removing the TargetEntry.
64266427
*/
64276428
static void
6428-
transform_cypher_merge_mark_tuple_position(List *target_list,
6429+
transform_cypher_merge_mark_tuple_position(cypher_parsestate *cpstate,
6430+
List *target_list,
64296431
cypher_create_path *path)
64306432
{
64316433
ListCell *lc = NULL;
@@ -6461,6 +6463,64 @@ transform_cypher_merge_mark_tuple_position(List *target_list,
64616463
// Mark the tuple position the target_node is for.
64626464
node->tuple_position = te->resno;
64636465
}
6466+
6467+
/* Iterate through the entities wrapping Var nodes with the volatile
6468+
* wrapper, if not already done.
6469+
*
6470+
* NOTE: add_volatile_wrapper function will not wrap itself so the following
6471+
* is safe to do.
6472+
*
6473+
* TODO: This ideally needs to be rewritten using a walker, to be more
6474+
* selective. However, walkers are tricky and take time to set up. For
6475+
* now, we brute force it. It is already restricted to explicitly
6476+
* named variables.
6477+
*
6478+
* TODO: We need to understand why add_volatile_wrapper is needed. Meaning,
6479+
* we need to understand why variables are removed by the function
6480+
* remove_unused_subquery_outputs. It "appears" that some linkage may
6481+
* not be set up properly, not allowing the PG logic to see that a
6482+
* variable is used from a previous clause. Right now, the volatile
6483+
* wrapper will suffice, but it is still a hack imho.
6484+
*
6485+
* TODO: There may be other locations where something similar may need to be
6486+
* done. This needs to be researched.
6487+
*/
6488+
foreach (lc, cpstate->entities)
6489+
{
6490+
transform_entity *te = lfirst(lc);
6491+
Node *node = (Node*) te->entity.node;
6492+
char *name = NULL;
6493+
6494+
if (is_ag_node(node, cypher_node))
6495+
{
6496+
name = te->entity.node->parsed_name;
6497+
}
6498+
else if (is_ag_node(node, cypher_relationship))
6499+
{
6500+
name = te->entity.rel->parsed_name;
6501+
}
6502+
else if (is_ag_node(node, cypher_path))
6503+
{
6504+
name = te->entity.path->parsed_var_name;
6505+
}
6506+
else
6507+
{
6508+
ereport(ERROR,
6509+
(errcode(ERRCODE_DATA_EXCEPTION),
6510+
errmsg("unexpected transform_entity entity type")));
6511+
}
6512+
6513+
/* node needs to have a parsed_name, meaning a name from the query */
6514+
if (name != NULL)
6515+
{
6516+
TargetEntry *tle = findTarget(target_list, name);
6517+
6518+
if (tle != NULL && IsA(tle->expr, Var))
6519+
{
6520+
tle->expr = add_volatile_wrapper(tle->expr);
6521+
}
6522+
}
6523+
}
64646524
}
64656525

64666526
/*

src/backend/parser/cypher_gram.y

+5
Original file line numberDiff line numberDiff line change
@@ -1206,6 +1206,7 @@ path:
12061206

12071207
p = (cypher_path *)$3;
12081208
p->var_name = $1;
1209+
p->parsed_var_name = $1;
12091210
p->location = @1;
12101211

12111212
$$ = (Node *)p;
@@ -1221,6 +1222,7 @@ anonymous_path:
12211222
n = make_ag_node(cypher_path);
12221223
n->path = $1;
12231224
n->var_name = NULL;
1225+
n->parsed_var_name = NULL;
12241226
n->location = @1;
12251227

12261228
$$ = (Node *)n;
@@ -1271,6 +1273,7 @@ path_node:
12711273

12721274
n = make_ag_node(cypher_node);
12731275
n->name = $2;
1276+
n->parsed_name = $2;
12741277
n->label = $3;
12751278
n->parsed_label = $3;
12761279
n->props = $4;
@@ -1317,6 +1320,7 @@ path_relationship_body:
13171320

13181321
n = make_ag_node(cypher_relationship);
13191322
n->name = $2;
1323+
n->parsed_name = $2;
13201324
n->label = $3;
13211325
n->parsed_label = $3;
13221326
n->varlen = $4;
@@ -1331,6 +1335,7 @@ path_relationship_body:
13311335

13321336
n = make_ag_node(cypher_relationship);
13331337
n->name = NULL;
1338+
n->parsed_name = NULL;
13341339
n->label = NULL;
13351340
n->parsed_label = NULL;
13361341
n->varlen = NULL;

src/include/nodes/cypher_nodes.h

+3
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ typedef struct cypher_path
133133
ExtensibleNode extensible;
134134
List *path; // [ node ( , relationship , node , ... ) ]
135135
char *var_name;
136+
char *parsed_var_name;
136137
int location;
137138
} cypher_path;
138139

@@ -141,6 +142,7 @@ typedef struct cypher_node
141142
{
142143
ExtensibleNode extensible;
143144
char *name;
145+
char *parsed_name;
144146
char *label;
145147
char *parsed_label;
146148
Node *props; // map or parameter
@@ -159,6 +161,7 @@ typedef struct cypher_relationship
159161
{
160162
ExtensibleNode extensible;
161163
char *name;
164+
char *parsed_name;
162165
char *label;
163166
char *parsed_label;
164167
Node *props; // map or parameter

0 commit comments

Comments
 (0)