From 55ea877470fdd86169cd7f0c804c62cd8210fd7a Mon Sep 17 00:00:00 2001 From: John Gemignani Date: Fri, 16 Jun 2023 13:36:46 -0700 Subject: [PATCH] Fix MERGE variable reuse Fixed the MERGE clause to allow for correct variable reuse in both the transform and execution phase. Fixed an incorrect usage in MATCH where a variable was compared with pg_strcasecmp instead of strcmp. Refactored some of the code using the volatile wrapper. Verified and corrected old regression tests that were in error. Added regression tests. --- regress/expected/cypher_match.out | 9 + regress/expected/cypher_merge.out | 160 +++++++++++++++-- regress/sql/cypher_match.sql | 4 + regress/sql/cypher_merge.sql | 41 ++++- src/backend/executor/cypher_create.c | 28 ++- src/backend/executor/cypher_merge.c | 25 ++- src/backend/parser/cypher_clause.c | 257 ++++++++++++++++++++++++--- 7 files changed, 466 insertions(+), 58 deletions(-) diff --git a/regress/expected/cypher_match.out b/regress/expected/cypher_match.out index 50eebeb43..aaf462272 100644 --- a/regress/expected/cypher_match.out +++ b/regress/expected/cypher_match.out @@ -2174,6 +2174,15 @@ SELECT * FROM cypher('cypher_match', $$ CREATE () WITH * MATCH (x{n0:x.n1}) RETU --- (0 rows) +-- these should fail due to multiple labels for a variable +SELECT * FROM cypher('cypher_match', $$ MATCH p=(x)-[]->(x:R) RETURN p, x $$) AS (p agtype, x agtype); +ERROR: multiple labels for variable 'x' are not supported +LINE 1: ...* FROM cypher('cypher_match', $$ MATCH p=(x)-[]->(x:R) RETUR... + ^ +SELECT * FROM cypher('cypher_match', $$ MATCH p=(x:r)-[]->(x:R) RETURN p, x $$) AS (p agtype, x agtype); +ERROR: multiple labels for variable 'x' are not supported +LINE 1: ...FROM cypher('cypher_match', $$ MATCH p=(x:r)-[]->(x:R) RETUR... + ^ -- -- Clean up -- diff --git a/regress/expected/cypher_merge.out b/regress/expected/cypher_merge.out index ecfc0c1d1..ab99aebea 100644 --- a/regress/expected/cypher_merge.out +++ b/regress/expected/cypher_merge.out @@ -904,9 +904,7 @@ SELECT * FROM cypher('cypher_merge', $$ MATCH (n:node) RETURN n $$) AS (n agtype -- -- Complex MERGE w/wo RETURN values -- --- These should each create a path, if it doesn't already exist. --- TODO Until the issue with variable reuse of 'x' in MERGE is corrected, --- these commands will each create a new path. +-- The first one should create a path, the others should just return parts of it. SELECT * FROM cypher('cypher_merge', $$ MERGE ()-[:B]->(x:C)-[:E]->(x:C)<-[f:F]-(y:I) $$) AS (x agtype); x --- @@ -915,7 +913,7 @@ SELECT * FROM cypher('cypher_merge', $$ MERGE ()-[:B]->(x:C)-[:E]->(x:C)<-[f:F]- SELECT * FROM cypher('cypher_merge', $$ MERGE ()-[:B]->(x:C)-[:E]->(x:C)<-[f:F]-(y:I) RETURN x $$) AS (x agtype); x ------------------------------------------------------------------ - {"id": 3096224743817220, "label": "C", "properties": {}}::vertex + {"id": 3096224743817217, "label": "C", "properties": {}}::vertex (1 row) SELECT * FROM cypher('cypher_merge', $$ MERGE p=()-[:B]->(x:C)-[:E]->(x:C)<-[f:F]-(y:I) $$) AS (p agtype); @@ -926,28 +924,154 @@ SELECT * FROM cypher('cypher_merge', $$ MERGE p=()-[:B]->(x:C)-[:E]->(x:C)<-[f:F SELECT * FROM cypher('cypher_merge', $$ MERGE p=()-[:B]->(x:C)-[:E]->(x:C)<-[f:F]-(y:I) RETURN p $$) AS (p agtype); p ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 281474976710708, "label": "", "properties": {}}::vertex, {"id": 2814749767106564, "label": "B", "end_id": 3096224743817223, "start_id": 281474976710708, "properties": {}}::edge, {"id": 3096224743817223, "label": "C", "properties": {}}::vertex, {"id": 3377699720527876, "label": "E", "end_id": 3096224743817224, "start_id": 3096224743817223, "properties": {}}::edge, {"id": 3096224743817224, "label": "C", "properties": {}}::vertex, {"id": 3659174697238532, "label": "F", "end_id": 3096224743817224, "start_id": 3940649673949188, "properties": {}}::edge, {"id": 3940649673949188, "label": "I", "properties": {}}::vertex]::path + [{"id": 281474976710705, "label": "", "properties": {}}::vertex, {"id": 2814749767106561, "label": "B", "end_id": 3096224743817217, "start_id": 281474976710705, "properties": {}}::edge, {"id": 3096224743817217, "label": "C", "properties": {}}::vertex, {"id": 3377699720527873, "label": "E", "end_id": 3096224743817217, "start_id": 3096224743817217, "properties": {}}::edge, {"id": 3096224743817217, "label": "C", "properties": {}}::vertex, {"id": 3659174697238529, "label": "F", "end_id": 3096224743817217, "start_id": 3940649673949185, "properties": {}}::edge, {"id": 3940649673949185, "label": "I", "properties": {}}::vertex]::path (1 row) SELECT * FROM cypher('cypher_merge', $$ MERGE p=()-[:B]->(x:C)-[:E]->(x:C)<-[f:F]-(y:I) RETURN p $$) AS (p agtype); p ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 281474976710709, "label": "", "properties": {}}::vertex, {"id": 2814749767106565, "label": "B", "end_id": 3096224743817225, "start_id": 281474976710709, "properties": {}}::edge, {"id": 3096224743817225, "label": "C", "properties": {}}::vertex, {"id": 3377699720527877, "label": "E", "end_id": 3096224743817226, "start_id": 3096224743817225, "properties": {}}::edge, {"id": 3096224743817226, "label": "C", "properties": {}}::vertex, {"id": 3659174697238533, "label": "F", "end_id": 3096224743817226, "start_id": 3940649673949189, "properties": {}}::edge, {"id": 3940649673949189, "label": "I", "properties": {}}::vertex]::path + [{"id": 281474976710705, "label": "", "properties": {}}::vertex, {"id": 2814749767106561, "label": "B", "end_id": 3096224743817217, "start_id": 281474976710705, "properties": {}}::edge, {"id": 3096224743817217, "label": "C", "properties": {}}::vertex, {"id": 3377699720527873, "label": "E", "end_id": 3096224743817217, "start_id": 3096224743817217, "properties": {}}::edge, {"id": 3096224743817217, "label": "C", "properties": {}}::vertex, {"id": 3659174697238529, "label": "F", "end_id": 3096224743817217, "start_id": 3940649673949185, "properties": {}}::edge, {"id": 3940649673949185, "label": "I", "properties": {}}::vertex]::path (1 row) --- TODO This should only return 1 row, as the path should already exist. --- However, we need to fix the variable reuse in MERGE. Until then, --- this will always return 5 rows due to 'x' above not being the same node. +-- This should only return 1 row, as the path should already exist. SELECT * FROM cypher('cypher_merge', $$ MATCH p=()-[:B]->(:C)-[:E]->(:C)<-[:F]-(:I) RETURN p $$) AS (p agtype); p ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 281474976710705, "label": "", "properties": {}}::vertex, {"id": 2814749767106561, "label": "B", "end_id": 3096224743817217, "start_id": 281474976710705, "properties": {}}::edge, {"id": 3096224743817217, "label": "C", "properties": {}}::vertex, {"id": 3377699720527873, "label": "E", "end_id": 3096224743817218, "start_id": 3096224743817217, "properties": {}}::edge, {"id": 3096224743817218, "label": "C", "properties": {}}::vertex, {"id": 3659174697238529, "label": "F", "end_id": 3096224743817218, "start_id": 3940649673949185, "properties": {}}::edge, {"id": 3940649673949185, "label": "I", "properties": {}}::vertex]::path - [{"id": 281474976710706, "label": "", "properties": {}}::vertex, {"id": 2814749767106562, "label": "B", "end_id": 3096224743817219, "start_id": 281474976710706, "properties": {}}::edge, {"id": 3096224743817219, "label": "C", "properties": {}}::vertex, {"id": 3377699720527874, "label": "E", "end_id": 3096224743817220, "start_id": 3096224743817219, "properties": {}}::edge, {"id": 3096224743817220, "label": "C", "properties": {}}::vertex, {"id": 3659174697238530, "label": "F", "end_id": 3096224743817220, "start_id": 3940649673949186, "properties": {}}::edge, {"id": 3940649673949186, "label": "I", "properties": {}}::vertex]::path - [{"id": 281474976710707, "label": "", "properties": {}}::vertex, {"id": 2814749767106563, "label": "B", "end_id": 3096224743817221, "start_id": 281474976710707, "properties": {}}::edge, {"id": 3096224743817221, "label": "C", "properties": {}}::vertex, {"id": 3377699720527875, "label": "E", "end_id": 3096224743817222, "start_id": 3096224743817221, "properties": {}}::edge, {"id": 3096224743817222, "label": "C", "properties": {}}::vertex, {"id": 3659174697238531, "label": "F", "end_id": 3096224743817222, "start_id": 3940649673949187, "properties": {}}::edge, {"id": 3940649673949187, "label": "I", "properties": {}}::vertex]::path - [{"id": 281474976710708, "label": "", "properties": {}}::vertex, {"id": 2814749767106564, "label": "B", "end_id": 3096224743817223, "start_id": 281474976710708, "properties": {}}::edge, {"id": 3096224743817223, "label": "C", "properties": {}}::vertex, {"id": 3377699720527876, "label": "E", "end_id": 3096224743817224, "start_id": 3096224743817223, "properties": {}}::edge, {"id": 3096224743817224, "label": "C", "properties": {}}::vertex, {"id": 3659174697238532, "label": "F", "end_id": 3096224743817224, "start_id": 3940649673949188, "properties": {}}::edge, {"id": 3940649673949188, "label": "I", "properties": {}}::vertex]::path - [{"id": 281474976710709, "label": "", "properties": {}}::vertex, {"id": 2814749767106565, "label": "B", "end_id": 3096224743817225, "start_id": 281474976710709, "properties": {}}::edge, {"id": 3096224743817225, "label": "C", "properties": {}}::vertex, {"id": 3377699720527877, "label": "E", "end_id": 3096224743817226, "start_id": 3096224743817225, "properties": {}}::edge, {"id": 3096224743817226, "label": "C", "properties": {}}::vertex, {"id": 3659174697238533, "label": "F", "end_id": 3096224743817226, "start_id": 3940649673949189, "properties": {}}::edge, {"id": 3940649673949189, "label": "I", "properties": {}}::vertex]::path -(5 rows) + [{"id": 281474976710705, "label": "", "properties": {}}::vertex, {"id": 2814749767106561, "label": "B", "end_id": 3096224743817217, "start_id": 281474976710705, "properties": {}}::edge, {"id": 3096224743817217, "label": "C", "properties": {}}::vertex, {"id": 3377699720527873, "label": "E", "end_id": 3096224743817217, "start_id": 3096224743817217, "properties": {}}::edge, {"id": 3096224743817217, "label": "C", "properties": {}}::vertex, {"id": 3659174697238529, "label": "F", "end_id": 3096224743817217, "start_id": 3940649673949185, "properties": {}}::edge, {"id": 3940649673949185, "label": "I", "properties": {}}::vertex]::path +(1 row) + +-- test variable reuse in MERGE - the first MERGE of each group should create, +-- the second MERGE shouldn't. +SELECT * FROM cypher('cypher_merge', $$ MATCH p=(x:P)-[:E]->(x:P) RETURN p, x $$) AS (p agtype, x agtype); + p | x +---+--- +(0 rows) + +SELECT * FROM cypher('cypher_merge', $$ MERGE (x:P)-[:E]->(x:P) $$) AS (x agtype); + x +--- +(0 rows) + +SELECT * FROM cypher('cypher_merge', $$ MERGE (x:P)-[:E]->(x) $$) AS (x agtype); + x +--- +(0 rows) + +SELECT * FROM cypher('cypher_merge', $$ MATCH p=(x:P)-[:E]->(x) RETURN p, x $$) AS (p agtype, x agtype); + p | x +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------ + [{"id": 4222124650659841, "label": "P", "properties": {}}::vertex, {"id": 3377699720527874, "label": "E", "end_id": 4222124650659841, "start_id": 4222124650659841, "properties": {}}::edge, {"id": 4222124650659841, "label": "P", "properties": {}}::vertex]::path | {"id": 4222124650659841, "label": "P", "properties": {}}::vertex +(1 row) + +SELECT * FROM cypher('cypher_merge', $$ MATCH p=(x:Q)-[:E]->(x:Q) RETURN p, x $$) AS (p agtype, x agtype); + p | x +---+--- +(0 rows) + +SELECT * FROM cypher('cypher_merge', $$ MERGE (x:Q)-[:E]->(x) $$) AS (x agtype); + x +--- +(0 rows) + +SELECT * FROM cypher('cypher_merge', $$ MERGE (x:Q)-[:E]->(x:Q) $$) AS (x agtype); + x +--- +(0 rows) + +SELECT * FROM cypher('cypher_merge', $$ MATCH p=(x:Q)-[:E]->(x) RETURN p, x $$) AS (p agtype, x agtype); + p | x +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------ + [{"id": 4503599627370497, "label": "Q", "properties": {}}::vertex, {"id": 3377699720527875, "label": "E", "end_id": 4503599627370497, "start_id": 4503599627370497, "properties": {}}::edge, {"id": 4503599627370497, "label": "Q", "properties": {}}::vertex]::path | {"id": 4503599627370497, "label": "Q", "properties": {}}::vertex +(1 row) +SELECT * FROM cypher('cypher_merge', $$ MATCH p=(x:R)-[:E]->(x) RETURN p, x $$) AS (p agtype, x agtype); + p | x +---+--- +(0 rows) + +SELECT * FROM cypher('cypher_merge', $$ MERGE p=(x:R)-[:E]->(x) RETURN p, x $$) AS (p agtype, x agtype); + p | x +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------ + [{"id": 4785074604081153, "label": "R", "properties": {}}::vertex, {"id": 3377699720527876, "label": "E", "end_id": 4785074604081153, "start_id": 4785074604081153, "properties": {}}::edge, {"id": 4785074604081153, "label": "R", "properties": {}}::vertex]::path | {"id": 4785074604081153, "label": "R", "properties": {}}::vertex +(1 row) + +SELECT * FROM cypher('cypher_merge', $$ MERGE p=(x:R)-[:E]->(x) RETURN p, x $$) AS (p agtype, x agtype); + p | x +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------ + [{"id": 4785074604081153, "label": "R", "properties": {}}::vertex, {"id": 3377699720527876, "label": "E", "end_id": 4785074604081153, "start_id": 4785074604081153, "properties": {}}::edge, {"id": 4785074604081153, "label": "R", "properties": {}}::vertex]::path | {"id": 4785074604081153, "label": "R", "properties": {}}::vertex +(1 row) + +SELECT * FROM cypher('cypher_merge', $$ MATCH p=(x:R)-[:E]->(x) RETURN p, x $$) AS (p agtype, x agtype); + p | x +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------ + [{"id": 4785074604081153, "label": "R", "properties": {}}::vertex, {"id": 3377699720527876, "label": "E", "end_id": 4785074604081153, "start_id": 4785074604081153, "properties": {}}::edge, {"id": 4785074604081153, "label": "R", "properties": {}}::vertex]::path | {"id": 4785074604081153, "label": "R", "properties": {}}::vertex +(1 row) + +-- should return 4 rows +SELECT * FROM cypher('cypher_merge', $$ MERGE p=(x)-[:E]->(x) RETURN p, x $$) AS (p agtype, x agtype); + p | x +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------ + [{"id": 3096224743817217, "label": "C", "properties": {}}::vertex, {"id": 3377699720527873, "label": "E", "end_id": 3096224743817217, "start_id": 3096224743817217, "properties": {}}::edge, {"id": 3096224743817217, "label": "C", "properties": {}}::vertex]::path | {"id": 3096224743817217, "label": "C", "properties": {}}::vertex + [{"id": 4222124650659841, "label": "P", "properties": {}}::vertex, {"id": 3377699720527874, "label": "E", "end_id": 4222124650659841, "start_id": 4222124650659841, "properties": {}}::edge, {"id": 4222124650659841, "label": "P", "properties": {}}::vertex]::path | {"id": 4222124650659841, "label": "P", "properties": {}}::vertex + [{"id": 4503599627370497, "label": "Q", "properties": {}}::vertex, {"id": 3377699720527875, "label": "E", "end_id": 4503599627370497, "start_id": 4503599627370497, "properties": {}}::edge, {"id": 4503599627370497, "label": "Q", "properties": {}}::vertex]::path | {"id": 4503599627370497, "label": "Q", "properties": {}}::vertex + [{"id": 4785074604081153, "label": "R", "properties": {}}::vertex, {"id": 3377699720527876, "label": "E", "end_id": 4785074604081153, "start_id": 4785074604081153, "properties": {}}::edge, {"id": 4785074604081153, "label": "R", "properties": {}}::vertex]::path | {"id": 4785074604081153, "label": "R", "properties": {}}::vertex +(4 rows) + +-- should create 1 row +SELECT * FROM cypher('cypher_merge', $$ MERGE p=(x)-[:E1]->(x) RETURN p, x $$) AS (p agtype, x agtype); + p | x +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------------- + [{"id": 281474976710706, "label": "", "properties": {}}::vertex, {"id": 5066549580791809, "label": "E1", "end_id": 281474976710706, "start_id": 281474976710706, "properties": {}}::edge, {"id": 281474976710706, "label": "", "properties": {}}::vertex]::path | {"id": 281474976710706, "label": "", "properties": {}}::vertex +(1 row) + +SELECT * FROM cypher('cypher_merge', $$ MATCH p=(x)-[:E1]->(x) RETURN p, x $$) AS (p agtype, x agtype); + p | x +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------------- + [{"id": 281474976710706, "label": "", "properties": {}}::vertex, {"id": 5066549580791809, "label": "E1", "end_id": 281474976710706, "start_id": 281474976710706, "properties": {}}::edge, {"id": 281474976710706, "label": "", "properties": {}}::vertex]::path | {"id": 281474976710706, "label": "", "properties": {}}::vertex +(1 row) + +-- the following should fail due to multiple labels +SELECT * FROM cypher('cypher_merge', $$ MERGE p=(x)-[:E]->(x:R) RETURN p, x $$) AS (p agtype, x agtype); +ERROR: multiple labels for variable 'x' are not supported +LINE 1: ...FROM cypher('cypher_merge', $$ MERGE p=(x)-[:E]->(x:R) RETUR... + ^ +SELECT * FROM cypher('cypher_merge', $$ MERGE p=(x:r)-[:E]->(x:R) RETURN p, x $$) AS (p agtype, x agtype); +ERROR: multiple labels for variable 'x' are not supported +LINE 1: ...OM cypher('cypher_merge', $$ MERGE p=(x:r)-[:E]->(x:R) RETUR... + ^ +SELECT * FROM cypher('cypher_merge', $$ MERGE (x)-[:E]->(x:R) $$) AS (x agtype); +ERROR: multiple labels for variable 'x' are not supported +LINE 1: ...* FROM cypher('cypher_merge', $$ MERGE (x)-[:E]->(x:R) $$) A... + ^ +SELECT * FROM cypher('cypher_merge', $$ MERGE (x:r)-[:E]->(x:R) $$) AS (x agtype); +ERROR: multiple labels for variable 'x' are not supported +LINE 1: ...FROM cypher('cypher_merge', $$ MERGE (x:r)-[:E]->(x:R) $$) A... + ^ +-- the following should fail due to reuse issues +SELECT * FROM cypher('cypher_merge', $$ MERGE (x:r)-[y:E]->(x)-[y]->(x) $$) AS (x agtype); +ERROR: a duplicate edge variable "y" is not permitted within a path +LINE 1: ... cypher('cypher_merge', $$ MERGE (x:r)-[y:E]->(x)-[y]->(x) $... + ^ +SELECT * FROM cypher('cypher_merge', $$ MERGE (x:r)-[y:E]->(x)-[x]->(y) $$) AS (x agtype); +ERROR: variable "x" is for an vertex +LINE 1: ... cypher('cypher_merge', $$ MERGE (x:r)-[y:E]->(x)-[x]->(y) $... + ^ +SELECT * FROM cypher('cypher_merge', $$ MERGE (x:r)-[y:E]->(x)-[z:E]->(y) $$) AS (x agtype); +ERROR: variable "y" is for a edge +LINE 1: ...'cypher_merge', $$ MERGE (x:r)-[y:E]->(x)-[z:E]->(y) $$) AS ... + ^ +SELECT * FROM cypher('cypher_merge', $$ MERGE p=(x:r)-[y:E]->(x)-[p]->(x) $$) AS (x agtype); +ERROR: variable "p" is for a path +LINE 1: ...ypher('cypher_merge', $$ MERGE p=(x:r)-[y:E]->(x)-[p]->(x) $... + ^ +SELECT * FROM cypher('cypher_merge', $$ MERGE p=(x:r)-[y:E]->(x)-[p:E]->(x) $$) AS (x agtype); +ERROR: variable "p" is for a path +LINE 1: ...ypher('cypher_merge', $$ MERGE p=(x:r)-[y:E]->(x)-[p:E]->(x)... + ^ +SELECT * FROM cypher('cypher_merge', $$ MERGE p=(x:r)-[y:E]->(p)-[x]->(y) $$) AS (x agtype); +ERROR: variable "p" is for a path +LINE 1: ...M cypher('cypher_merge', $$ MERGE p=(x:r)-[y:E]->(p)-[x]->(y... + ^ --clean up SELECT * FROM cypher('cypher_merge', $$MATCH (n) DETACH DELETE n $$) AS (a agtype); a @@ -958,7 +1082,7 @@ SELECT * FROM cypher('cypher_merge', $$MATCH (n) DETACH DELETE n $$) AS (a agtyp * Clean up graph */ SELECT drop_graph('cypher_merge', true); -NOTICE: drop cascades to 14 other objects +NOTICE: drop cascades to 18 other objects DETAIL: drop cascades to table cypher_merge._ag_label_vertex drop cascades to table cypher_merge._ag_label_edge drop cascades to table cypher_merge.e @@ -973,6 +1097,10 @@ drop cascades to table cypher_merge."C" drop cascades to table cypher_merge."E" drop cascades to table cypher_merge."F" drop cascades to table cypher_merge."I" +drop cascades to table cypher_merge."P" +drop cascades to table cypher_merge."Q" +drop cascades to table cypher_merge."R" +drop cascades to table cypher_merge."E1" NOTICE: graph "cypher_merge" has been dropped drop_graph ------------ diff --git a/regress/sql/cypher_match.sql b/regress/sql/cypher_match.sql index ffd554615..4228d82b3 100644 --- a/regress/sql/cypher_match.sql +++ b/regress/sql/cypher_match.sql @@ -977,6 +977,10 @@ SELECT * FROM cypher('cypher_match', $$ MATCH p=(a {name:a.name})-[u {relationsh SELECT * FROM cypher('cypher_match', $$ CREATE () WITH * MATCH (x{n0:x.n1}) RETURN 0 $$) as (a agtype); +-- these should fail due to multiple labels for a variable +SELECT * FROM cypher('cypher_match', $$ MATCH p=(x)-[]->(x:R) RETURN p, x $$) AS (p agtype, x agtype); +SELECT * FROM cypher('cypher_match', $$ MATCH p=(x:r)-[]->(x:R) RETURN p, x $$) AS (p agtype, x agtype); + -- -- Clean up -- diff --git a/regress/sql/cypher_merge.sql b/regress/sql/cypher_merge.sql index 1fe3ed707..81c679c5a 100644 --- a/regress/sql/cypher_merge.sql +++ b/regress/sql/cypher_merge.sql @@ -482,19 +482,48 @@ SELECT * FROM cypher('cypher_merge', $$ MATCH (n:node) RETURN n $$) AS (n agtype -- -- Complex MERGE w/wo RETURN values -- --- These should each create a path, if it doesn't already exist. --- TODO Until the issue with variable reuse of 'x' in MERGE is corrected, --- these commands will each create a new path. +-- The first one should create a path, the others should just return parts of it. SELECT * FROM cypher('cypher_merge', $$ MERGE ()-[:B]->(x:C)-[:E]->(x:C)<-[f:F]-(y:I) $$) AS (x agtype); SELECT * FROM cypher('cypher_merge', $$ MERGE ()-[:B]->(x:C)-[:E]->(x:C)<-[f:F]-(y:I) RETURN x $$) AS (x agtype); SELECT * FROM cypher('cypher_merge', $$ MERGE p=()-[:B]->(x:C)-[:E]->(x:C)<-[f:F]-(y:I) $$) AS (p agtype); SELECT * FROM cypher('cypher_merge', $$ MERGE p=()-[:B]->(x:C)-[:E]->(x:C)<-[f:F]-(y:I) RETURN p $$) AS (p agtype); SELECT * FROM cypher('cypher_merge', $$ MERGE p=()-[:B]->(x:C)-[:E]->(x:C)<-[f:F]-(y:I) RETURN p $$) AS (p agtype); --- TODO This should only return 1 row, as the path should already exist. --- However, we need to fix the variable reuse in MERGE. Until then, --- this will always return 5 rows due to 'x' above not being the same node. + +-- This should only return 1 row, as the path should already exist. SELECT * FROM cypher('cypher_merge', $$ MATCH p=()-[:B]->(:C)-[:E]->(:C)<-[:F]-(:I) RETURN p $$) AS (p agtype); +-- test variable reuse in MERGE - the first MERGE of each group should create, +-- the second MERGE shouldn't. +SELECT * FROM cypher('cypher_merge', $$ MATCH p=(x:P)-[:E]->(x:P) RETURN p, x $$) AS (p agtype, x agtype); +SELECT * FROM cypher('cypher_merge', $$ MERGE (x:P)-[:E]->(x:P) $$) AS (x agtype); +SELECT * FROM cypher('cypher_merge', $$ MERGE (x:P)-[:E]->(x) $$) AS (x agtype); +SELECT * FROM cypher('cypher_merge', $$ MATCH p=(x:P)-[:E]->(x) RETURN p, x $$) AS (p agtype, x agtype); +SELECT * FROM cypher('cypher_merge', $$ MATCH p=(x:Q)-[:E]->(x:Q) RETURN p, x $$) AS (p agtype, x agtype); +SELECT * FROM cypher('cypher_merge', $$ MERGE (x:Q)-[:E]->(x) $$) AS (x agtype); +SELECT * FROM cypher('cypher_merge', $$ MERGE (x:Q)-[:E]->(x:Q) $$) AS (x agtype); +SELECT * FROM cypher('cypher_merge', $$ MATCH p=(x:Q)-[:E]->(x) RETURN p, x $$) AS (p agtype, x agtype); +SELECT * FROM cypher('cypher_merge', $$ MATCH p=(x:R)-[:E]->(x) RETURN p, x $$) AS (p agtype, x agtype); +SELECT * FROM cypher('cypher_merge', $$ MERGE p=(x:R)-[:E]->(x) RETURN p, x $$) AS (p agtype, x agtype); +SELECT * FROM cypher('cypher_merge', $$ MERGE p=(x:R)-[:E]->(x) RETURN p, x $$) AS (p agtype, x agtype); +SELECT * FROM cypher('cypher_merge', $$ MATCH p=(x:R)-[:E]->(x) RETURN p, x $$) AS (p agtype, x agtype); +-- should return 4 rows +SELECT * FROM cypher('cypher_merge', $$ MERGE p=(x)-[:E]->(x) RETURN p, x $$) AS (p agtype, x agtype); +-- should create 1 row +SELECT * FROM cypher('cypher_merge', $$ MERGE p=(x)-[:E1]->(x) RETURN p, x $$) AS (p agtype, x agtype); +SELECT * FROM cypher('cypher_merge', $$ MATCH p=(x)-[:E1]->(x) RETURN p, x $$) AS (p agtype, x agtype); +-- the following should fail due to multiple labels +SELECT * FROM cypher('cypher_merge', $$ MERGE p=(x)-[:E]->(x:R) RETURN p, x $$) AS (p agtype, x agtype); +SELECT * FROM cypher('cypher_merge', $$ MERGE p=(x:r)-[:E]->(x:R) RETURN p, x $$) AS (p agtype, x agtype); +SELECT * FROM cypher('cypher_merge', $$ MERGE (x)-[:E]->(x:R) $$) AS (x agtype); +SELECT * FROM cypher('cypher_merge', $$ MERGE (x:r)-[:E]->(x:R) $$) AS (x agtype); +-- the following should fail due to reuse issues +SELECT * FROM cypher('cypher_merge', $$ MERGE (x:r)-[y:E]->(x)-[y]->(x) $$) AS (x agtype); +SELECT * FROM cypher('cypher_merge', $$ MERGE (x:r)-[y:E]->(x)-[x]->(y) $$) AS (x agtype); +SELECT * FROM cypher('cypher_merge', $$ MERGE (x:r)-[y:E]->(x)-[z:E]->(y) $$) AS (x agtype); +SELECT * FROM cypher('cypher_merge', $$ MERGE p=(x:r)-[y:E]->(x)-[p]->(x) $$) AS (x agtype); +SELECT * FROM cypher('cypher_merge', $$ MERGE p=(x:r)-[y:E]->(x)-[p:E]->(x) $$) AS (x agtype); +SELECT * FROM cypher('cypher_merge', $$ MERGE p=(x:r)-[y:E]->(p)-[x]->(y) $$) AS (x agtype); + --clean up SELECT * FROM cypher('cypher_merge', $$MATCH (n) DETACH DELETE n $$) AS (a agtype); diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index e1b9ec89d..083ddd50c 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -129,6 +129,12 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, cypher_node->id_expr_state = ExecInitExpr(cypher_node->id_expr, (PlanState *)node); } + + if (cypher_node->prop_expr != NULL) + { + cypher_node->prop_expr_state = ExecInitExpr(cypher_node->prop_expr, + (PlanState *)node); + } } } @@ -140,7 +146,9 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, * that have modified the command id. */ if (estate->es_output_cid == 0) + { estate->es_output_cid = estate->es_snapshot->curcid; + } Increment_Estate_CommandId(estate); } @@ -210,15 +218,19 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *node) Decrement_Estate_CommandId(estate) slot = ExecProcNode(node->ss.ps.lefttree); Increment_Estate_CommandId(estate) + /* break when there are no tuples */ if (TupIsNull(slot)) { break; } + /* setup the scantuple that the process_pattern needs */ econtext->ecxt_scantuple = node->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; + process_pattern(css); + /* * This may not be necessary. If we have an empty pattern, nothing was * inserted and the current command Id was not used. So, only flag it @@ -230,6 +242,7 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *node) used = true; } } while (terminal); + /* * If the current command Id wasn't used, nothing was inserted and we're * done. @@ -238,8 +251,10 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *node) { return NULL; } + /* update the current command Id */ CommandCounterIncrement(); + /* if this was a terminal CREATE just return NULL */ if (terminal) { @@ -256,19 +271,25 @@ static void end_cypher_create(CustomScanState *node) (cypher_create_custom_scan_state *)node; ListCell *lc; + // increment the command counter + CommandCounterIncrement(); + ExecEndNode(node->ss.ps.lefttree); foreach (lc, css->pattern) { cypher_create_path *path = lfirst(lc); ListCell *lc2; + foreach (lc2, path->target_nodes) { cypher_target_node *cypher_node = (cypher_target_node *)lfirst(lc2); if (!CYPHER_TARGET_NODE_INSERT_ENTITY(cypher_node->flags)) + { continue; + } // close all indices for the node ExecCloseIndices(cypher_node->resultRelInfo); @@ -506,8 +527,7 @@ static Datum create_vertex(cypher_create_custom_scan_state *css, scantuple = ps->ps_ExprContext->ecxt_scantuple; // make the vertex agtype - result = make_vertex( - id, CStringGetDatum(node->label_name), + result = make_vertex(id, CStringGetDatum(node->label_name), PointerGetDatum(scanTupleSlot->tts_values[node->prop_attr_num])); // append to the path list @@ -546,9 +566,11 @@ static Datum create_vertex(cypher_create_custom_scan_state *css, v = get_ith_agtype_value_from_container(&a->root, 0); if (v->type != AGTV_VERTEX) + { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("agtype must resolve to a vertex"))); + } // extract the id agtype field id_value = GET_AGTYPE_VALUE_OBJECT_VALUE(v, "id"); @@ -570,10 +592,12 @@ static Datum create_vertex(cypher_create_custom_scan_state *css, if (!SAFE_TO_SKIP_EXISTENCE_CHECK(node->flags)) { if (!entity_exists(estate, css->graph_oid, DATUM_GET_GRAPHID(id))) + { ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("vertex assigned to variable %s was deleted", node->variable_name))); + } } if (CYPHER_TARGET_NODE_IN_PATH(node->flags)) diff --git a/src/backend/executor/cypher_merge.c b/src/backend/executor/cypher_merge.c index b42408b75..67112dd0b 100644 --- a/src/backend/executor/cypher_merge.c +++ b/src/backend/executor/cypher_merge.c @@ -198,27 +198,25 @@ static bool check_path(cypher_merge_custom_scan_state *css, */ if (slot->tts_isnull[node->tuple_position - 1]) { + return true; } } - } - return false; } static void process_path(cypher_merge_custom_scan_state *css) { cypher_create_path *path = css->path; - ListCell *lc = list_head(path->target_nodes); /* * Create the first vertex. The create_vertex function will * create the rest of the path, if necessary. */ - merge_vertex(css, lfirst(lc), lnext(lc)); + merge_vertex(css, lfirst(lc), lnext(lc)); /* * If this path is a variable, take the list that was accumulated @@ -272,9 +270,13 @@ static void process_simple_merge(CustomScanState *node) if (TupIsNull(slot)) { ExprContext *econtext = node->ss.ps.ps_ExprContext; + SubqueryScanState *sss = (SubqueryScanState *)node->ss.ps.lefttree; + + /* our child execution node should be a subquery */ + Assert(IsA(sss, SubqueryScanState)); /* setup the scantuple that the process_path needs */ - econtext->ecxt_scantuple = node->ss.ps.lefttree->ps_ResultTupleSlot; + econtext->ecxt_scantuple = sss->ss.ss_ScanTupleSlot; econtext->ecxt_scantuple->tts_isempty = false; process_path(css); @@ -334,7 +336,7 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) /* * Case 1: MERGE is not the first clause in the cypher query. * - * For this case, we need to process all tuples give to us by the + * For this case, we need to process all tuples given to us by the * previous clause. When we receive a tuple from the previous clause: * check to see if the left lateral join found the pattern already. If * it did, we don't need to create the pattern. If the lateral join did @@ -377,7 +379,6 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) return NULL; } - //return ExecProject(node->ss.ps.ps_ProjInfo); econtext->ecxt_scantuple = ExecProject(node->ss.ps.lefttree->ps_ProjInfo); return ExecProject(node->ss.ps.ps_ProjInfo); @@ -783,13 +784,9 @@ static Datum merge_vertex(cypher_merge_custom_scan_state *css, Datum d; agtype_value *v = NULL; agtype_value *id_value = NULL; - TupleTableSlot *scantuple = NULL; - PlanState *ps = NULL; - - ps = css->css.ss.ps.lefttree; - scantuple = ps->ps_ExprContext->ecxt_scantuple; - if (scantuple->tts_isnull[node->tuple_position - 1]) + /* check that the variable isn't NULL */ + if (scanTupleSlot->tts_isnull[node->tuple_position - 1]) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -798,7 +795,7 @@ static Datum merge_vertex(cypher_merge_custom_scan_state *css, } /* get the vertex agtype in the scanTupleSlot */ - d = scantuple->tts_values[node->tuple_position - 1]; + d = scanTupleSlot->tts_values[node->tuple_position - 1]; a = DATUM_GET_AGTYPE_P(d); /* Convert to an agtype value */ diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 910104bcf..8697729df 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -193,6 +193,7 @@ static Expr *cypher_create_properties(cypher_parsestate *cpstate, enum transform_entity_type type); static Expr *add_volatile_wrapper(Expr *node); static bool variable_exists(cypher_parsestate *cpstate, char *name); +static void add_volatile_wrapper_to_target_entry(List *target_list, int resno); static int get_target_entry_resno(List *target_list, char *name); static void handle_prev_clause(cypher_parsestate *cpstate, Query *query, cypher_clause *clause, bool first_rte); @@ -263,6 +264,9 @@ static cypher_clause *convert_merge_to_match(cypher_merge *merge); static void transform_cypher_merge_mark_tuple_position(List *target_list, cypher_create_path *path); +static cypher_target_node *get_referenced_variable(ParseState *pstate, + Node *node, + List *transformed_path); //call...[yield] static Query *transform_cypher_call_stmt(cypher_parsestate *cpstate, @@ -1393,7 +1397,6 @@ static List *transform_cypher_delete_item_list(cypher_parsestate *cpstate, } resno = get_target_entry_resno(query->targetList, val->val.str); - if (resno == -1) { ereport(ERROR, @@ -1403,6 +1406,8 @@ static List *transform_cypher_delete_item_list(cypher_parsestate *cpstate, parser_errposition(pstate, col->location))); } + add_volatile_wrapper_to_target_entry(query->targetList, resno); + pos = makeInteger(resno); item->var_name = val->val.str; @@ -1551,9 +1556,9 @@ cypher_update_information *transform_cypher_remove_item_list( variable_name = variable_node->val.str; item->var_name = variable_name; + item->entity_position = get_target_entry_resno(query->targetList, variable_name); - if (item->entity_position == -1) { ereport(ERROR, @@ -1563,6 +1568,9 @@ cypher_update_information *transform_cypher_remove_item_list( parser_errposition(pstate, set_item->location))); } + add_volatile_wrapper_to_target_entry(query->targetList, + item->entity_position); + // extract property name if (list_length(ind->indirection) != 1) { @@ -1726,9 +1734,9 @@ cypher_update_information *transform_cypher_set_item_list( variable_name = variable_node->val.str; item->var_name = variable_name; + item->entity_position = get_target_entry_resno(query->targetList, variable_name); - if (item->entity_position == -1) { ereport(ERROR, @@ -1738,6 +1746,9 @@ cypher_update_information *transform_cypher_set_item_list( parser_errposition(pstate, set_item->location))); } + add_volatile_wrapper_to_target_entry(query->targetList, + item->entity_position); + // set keep_null property if (is_ag_node(set_item->expr, cypher_map)) { @@ -4395,7 +4406,7 @@ static Expr *transform_cypher_edge(cypher_parsestate *cpstate, if (refs_var && (cr->parsed_label != NULL || rel->parsed_label != NULL) && (cr->parsed_label == NULL || rel->parsed_label == NULL || - (pg_strcasecmp(cr->parsed_label, rel->parsed_label) != 0))) + (strcmp(cr->parsed_label, rel->parsed_label) != 0))) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -4600,7 +4611,7 @@ static Expr *transform_cypher_node(cypher_parsestate *cpstate, if (refs_var && (cn->parsed_label != NULL || node->parsed_label != NULL) && (cn->parsed_label == NULL || node->parsed_label == NULL || - (pg_strcasecmp(cn->parsed_label, node->parsed_label) != 0))) + (strcmp(cn->parsed_label, node->parsed_label) != 0))) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -4927,7 +4938,6 @@ transform_cypher_create_path(cypher_parsestate *cpstate, List **target_list, } rel->flags |= CYPHER_TARGET_NODE_IN_PATH_VAR; } - transformed_path = lappend(transformed_path, rel); @@ -5213,9 +5223,6 @@ transform_create_cypher_node(cypher_parsestate *cpstate, List **target_list, } /* - * TODO A function called get_ should NOT modify the contents of what - * it gets. This needs to be fixed. - * * Returns the resno for the TargetEntry with the resname equal to the name * passed. Returns -1 otherwise. */ @@ -5228,7 +5235,6 @@ static int get_target_entry_resno(List *target_list, char *name) TargetEntry *te = (TargetEntry *)lfirst(lc); if (!strcmp(te->resname, name)) { - te->expr = add_volatile_wrapper(te->expr); return te->resno; } } @@ -5236,6 +5242,32 @@ static int get_target_entry_resno(List *target_list, char *name) return -1; } +/* adds the volatile wrapper to the specified target entry */ +static void add_volatile_wrapper_to_target_entry(List *target_list, int resno) +{ + ListCell *lc; + + Assert(target_list != NULL); + Assert(resno >= 0); + + /* find the resource */ + foreach (lc, target_list) + { + TargetEntry *te = (TargetEntry *)lfirst(lc); + if (te->resno == resno) + { + /* wrap it */ + te->expr = add_volatile_wrapper(te->expr); + return; + } + } + + /* if we didn't find anything, there was a problem */ + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("add_volatile_wrapper_to_target_entry: resno not found"))); +} + /* * Transform logic for a previously declared variable in a CREATE clause. * All we need from the variable node is its id, and whether we can skip @@ -5253,7 +5285,6 @@ static cypher_target_node *transform_create_cypher_existing_node( rel->resultRelInfo = NULL; rel->variable_name = node->name; - if (node->props) { ereport(ERROR, @@ -5283,6 +5314,8 @@ static cypher_target_node *transform_create_cypher_existing_node( */ rel->tuple_position = get_target_entry_resno(*target_list, node->name); + add_volatile_wrapper_to_target_entry(*target_list, rel->tuple_position); + return rel; } @@ -5632,6 +5665,12 @@ static Expr *add_volatile_wrapper(Expr *node) oid = get_ag_func_oid("agtype_volatile_wrapper", 1, ANYOID); + /* if the passed Expr node is already wrapped, just return it */ + if (IsA(node, FuncExpr) && oid == ((FuncExpr*)node)->funcid) + { + return node; + } + return (Expr *)makeFuncExpr(oid, AGTYPEOID, list_make1(node), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); } @@ -5766,7 +5805,10 @@ static Query *transform_cypher_merge(cypher_parsestate *cpstate, else { // make the merge node into a match node - cypher_clause *merge_clause_as_match = convert_merge_to_match(self); + + // TODO this is called above and appears redundant but needs to be + // looked into + //cypher_clause *merge_clause_as_match = convert_merge_to_match(self); /* * Create the metadata needed for creating missing paths. @@ -5867,7 +5909,7 @@ transform_merge_make_lateral_join(cypher_parsestate *cpstate, Query *query, * transform the previous clause */ j->larg = transform_clause_for_join(cpstate, clause->prev, &l_rte, - &l_nsitem, l_alias); + &l_nsitem, l_alias); pstate->p_namespace = lappend(pstate->p_namespace, l_nsitem); /* @@ -5995,6 +6037,134 @@ transform_cypher_merge_mark_tuple_position(List *target_list, } } +/* + * Helper function to return a shallow copy of an existing, already transformed, + * matching variable. The copy returned will be flagged as such. If none are + * found, it will return NULL. If it finds a mismatched type, it will error + * stating that. + */ +static cypher_target_node *get_referenced_variable(ParseState *pstate, + Node *node, + List *transformed_path) +{ + ListCell *lc = NULL; + char *node_name = NULL; + char *node_label = NULL; + char node_type = 0; + int node_loc = -1; + + /* passed node should only be a vertex or an edge */ + Assert(is_ag_node(node, cypher_node) || + is_ag_node(node, cypher_relationship)); + + /* set up our search based on our input type */ + if (is_ag_node(node, cypher_node)) + { + node_name = ((cypher_node *)node)->name; + node_label = ((cypher_node *)node)->label; + node_loc = ((cypher_node *)node)->location; + node_type = 'v'; + } + else + { + node_name = ((cypher_relationship *)node)->name; + node_label = ((cypher_relationship *)node)->label; + node_loc = ((cypher_relationship *)node)->location; + node_type = 'e'; + } + + /* look through the list of previously transformed nodes and edges */ + foreach (lc, transformed_path) + { + cypher_target_node *ctn = NULL; + bool is_name = false; + bool is_label = false; + + /* list items should be of type cypher_target_node */ + Assert(is_ag_node(lfirst(lc), cypher_target_node)); + ctn = lfirst(lc); + + /* do they have names? if so, do they match? */ + is_name = (node_name == NULL || ctn->variable_name == NULL) ? + false : strcmp(node_name, ctn->variable_name) == 0; + + /* do they have labels? if so, do they match? */ + is_label = (ctn->label_name != NULL) ? + ((node_label == NULL) ? true : strcmp(ctn->label_name, node_label) == 0) + : false; + + /* if the types don't match, error or skip */ + if (node_type != ctn->type) + { + /* is the name a match, generate an error. otherwise, skip it. */ + if (is_name) + { + if (node_type == 'v') + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("variable \"%s\" is for a edge", + node_name), + parser_errposition(pstate, node_loc))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("variable \"%s\" is for an vertex", + node_name), + parser_errposition(pstate, node_loc))); + } + } + else + { + continue; + } + } + + if (is_name && !is_label) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("multiple labels for variable '%s' are not supported", + node_name), + parser_errposition(pstate, node_loc))); + } + + /* + * If this is a match, make a shallow copy of it, modify the copy to be + * flagged as a previously declared variable, and then return it. + */ + if (is_name && is_label) + { + cypher_target_node *_cpy = make_ag_node(cypher_target_node); + + /* make a shallow copy */ + _cpy->type = ctn->type; + _cpy->flags = ctn->flags; + _cpy->dir = ctn->dir; + _cpy->id_expr = ctn->id_expr; + _cpy->id_expr_state = ctn->id_expr_state; + _cpy->prop_expr = ctn->prop_expr; + _cpy->prop_expr_state = ctn->prop_expr_state; + _cpy->prop_attr_num = ctn->prop_attr_num; + _cpy->resultRelInfo = ctn->resultRelInfo; + _cpy->elemTupleSlot = ctn->elemTupleSlot; + _cpy->relid = ctn->relid; + _cpy->label_name = ctn->label_name; + _cpy->variable_name = ctn->variable_name; + _cpy->tuple_position = ctn->tuple_position; + + /* set it to a declared variable */ + _cpy->flags &= 0xfffffffe; + _cpy->flags |= EXISTING_VARIABLE_DECLARED_SAME_CLAUSE; + + return _cpy; + } + } + return NULL; +} + /* * Creates the target nodes for a merge path. If MERGE has a path that doesn't * exist then in the MERGE clause we act like a CREATE clause. This function @@ -6004,6 +6174,7 @@ static cypher_create_path * transform_cypher_merge_path(cypher_parsestate *cpstate, List **target_list, cypher_path *path) { + ParseState *pstate = (ParseState *)cpstate; ListCell *lc; List *transformed_path = NIL; cypher_create_path *ccp = make_ag_node(cypher_create_path); @@ -6016,9 +6187,30 @@ transform_cypher_merge_path(cypher_parsestate *cpstate, List **target_list, if (is_ag_node(lfirst(lc), cypher_node)) { cypher_node *node = lfirst(lc); + cypher_target_node *rel = NULL; - cypher_target_node *rel = - transform_merge_cypher_node(cpstate, target_list, node); + if (path->var_name != NULL && node->name != NULL && + strcmp(path->var_name, node->name) == 0) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("variable \"%s\" is for a path", node->name), + parser_errposition(pstate, node->location))); + } + + /* + * If the variable was already transformed, get a referenced copy of + * it. This copy will make sure the executor phase doesn't create a + * new node from it. + */ + rel = get_referenced_variable(pstate, (Node *)node, + transformed_path); + + /* if there wasn't a transformed variable, transform the node */ + if (rel == NULL) + { + rel = transform_merge_cypher_node(cpstate, target_list, node); + } if (in_path) { @@ -6029,10 +6221,37 @@ transform_cypher_merge_path(cypher_parsestate *cpstate, List **target_list, } else if (is_ag_node(lfirst(lc), cypher_relationship)) { - cypher_relationship *edge = lfirst(lc); + cypher_relationship *edge = NULL; + cypher_target_node *rel = NULL; - cypher_target_node *rel = - transform_merge_cypher_edge(cpstate, target_list, edge); + edge = lfirst(lc); + + if (path->var_name != NULL && edge->name != NULL && + strcmp(path->var_name, edge->name) == 0) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("variable \"%s\" is for a path", edge->name), + parser_errposition(pstate, edge->location))); + } + + /* + * Get a referenced edge variable. This should not happen as edges + * can not be duplicated within a path. + */ + rel = get_referenced_variable(pstate, (Node *)edge, + transformed_path); + if (rel != NULL) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("a duplicate edge variable \"%s\" is not permitted within a path", + edge->name), + parser_errposition(pstate, edge->location))); + } + + /* transform the edge */ + rel = transform_merge_cypher_edge(cpstate, target_list, edge); if (in_path) { @@ -6189,10 +6408,8 @@ transform_merge_cypher_node(cypher_parsestate *cpstate, List **target_list, if (node->name != NULL) { - transform_entity *entity = find_transform_entity(cpstate, node->name, ENT_VERTEX); - /* * the vertex was previously declared, we do not need to do any setup * to create the node.