From dc05c64561076ee9a212d9646270d9ba2716be90 Mon Sep 17 00:00:00 2001 From: John Gemignani Date: Wed, 15 Mar 2023 17:56:32 -0700 Subject: [PATCH] Fix property constraints against resolved variables (#724) Fixed an issue where an already resolved variable, when used for a property constraint, errored out. See #724 for more details. This also fixed a regression test that was erroring out, but was overlooked. Adjusted and added additional regression tests. Co-authored-by: Dehowe Feng --- regress/expected/cypher_match.out | 225 ++++++++++++++++++++++++++++- regress/expected/cypher_merge.out | 40 ++--- regress/sql/cypher_match.sql | 73 ++++++++++ src/backend/parser/cypher_clause.c | 167 ++++++++++++++++++--- 4 files changed, 461 insertions(+), 44 deletions(-) diff --git a/regress/expected/cypher_match.out b/regress/expected/cypher_match.out index 4888556d9..f3817a50d 100644 --- a/regress/expected/cypher_match.out +++ b/regress/expected/cypher_match.out @@ -1450,11 +1450,233 @@ $$) as (n agtype); {"id": 281474976710662, "label": "", "properties": {"i": 1, "j": 2, "k": 3}}::vertex (1 row) +-- +-- Regression tests to check previous clause variable refs +-- +-- set up initial state and show what we're working with +SELECT * FROM cypher('cypher_match', $$ + CREATE (a {age: 4}) RETURN a $$) as (a agtype); + a +------------------------------------------------------------------------ + {"id": 281474976710665, "label": "", "properties": {"age": 4}}::vertex +(1 row) + +SELECT * FROM cypher('cypher_match', $$ + CREATE (b {age: 6}) RETURN b $$) as (b agtype); + b +------------------------------------------------------------------------ + {"id": 281474976710666, "label": "", "properties": {"age": 6}}::vertex +(1 row) + +SELECT * FROM cypher('cypher_match', $$ + MATCH (a) RETURN a $$) as (a agtype); + a +-------------------------------------------------------------------------------------- + {"id": 281474976710659, "label": "", "properties": {"name": "orphan"}}::vertex + {"id": 281474976710660, "label": "", "properties": {"name": "F"}}::vertex + {"id": 281474976710661, "label": "", "properties": {"name": "T"}}::vertex + {"id": 281474976710662, "label": "", "properties": {"i": 1, "j": 2, "k": 3}}::vertex + {"id": 281474976710663, "label": "", "properties": {"i": 1, "j": 3}}::vertex + {"id": 281474976710664, "label": "", "properties": {"i": 2, "k": 3}}::vertex + {"id": 281474976710665, "label": "", "properties": {"age": 4}}::vertex + {"id": 281474976710666, "label": "", "properties": {"age": 6}}::vertex +(8 rows) + +SELECT * FROM cypher('cypher_match', $$ + MATCH (a) WHERE exists(a.name) RETURN a $$) as (a agtype); + a +-------------------------------------------------------------------------------- + {"id": 281474976710659, "label": "", "properties": {"name": "orphan"}}::vertex + {"id": 281474976710660, "label": "", "properties": {"name": "F"}}::vertex + {"id": 281474976710661, "label": "", "properties": {"name": "T"}}::vertex +(3 rows) + +SELECT * FROM cypher('cypher_match', $$ + MATCH (a) WHERE exists(a.name) SET a.age = 4 RETURN a $$) as (a agtype); + a +------------------------------------------------------------------------------------------ + {"id": 281474976710659, "label": "", "properties": {"age": 4, "name": "orphan"}}::vertex + {"id": 281474976710660, "label": "", "properties": {"age": 4, "name": "F"}}::vertex + {"id": 281474976710661, "label": "", "properties": {"age": 4, "name": "T"}}::vertex +(3 rows) + +SELECT * FROM cypher('cypher_match', $$ + MATCH (a),(b) WHERE a.age = 4 AND a.name = "T" AND b.age = 6 + RETURN a,b $$) as (a agtype, b agtype); + a | b +-------------------------------------------------------------------------------------+------------------------------------------------------------------------ + {"id": 281474976710661, "label": "", "properties": {"age": 4, "name": "T"}}::vertex | {"id": 281474976710666, "label": "", "properties": {"age": 6}}::vertex +(1 row) + +SELECT * FROM cypher('cypher_match', $$ + MATCH (a),(b) WHERE a.age = 4 AND a.name = "T" AND b.age = 6 CREATE + (a)-[:knows {relationship: "friends", years: 3}]->(b) $$) as (r agtype); + r +--- +(0 rows) + +SELECT * FROM cypher('cypher_match', $$ + MATCH (a),(b) WHERE a.age = 4 AND a.name = "orphan" AND b.age = 6 CREATE + (a)-[:knows {relationship: "enemies", years: 4}]->(b) $$) as (r agtype); + r +--- +(0 rows) + +SELECT * FROM cypher('cypher_match', $$ + MATCH (a)-[r]-(b) RETURN r $$) as (r agtype); + r +----------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"id": 4785074604081153, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710661, "properties": {"years": 3, "relationship": "friends"}}::edge + {"id": 4785074604081153, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710661, "properties": {"years": 3, "relationship": "friends"}}::edge + {"id": 4785074604081154, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710659, "properties": {"years": 4, "relationship": "enemies"}}::edge + {"id": 4785074604081154, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710659, "properties": {"years": 4, "relationship": "enemies"}}::edge + {"id": 1407374883553283, "label": "e1", "end_id": 281474976710661, "start_id": 281474976710660, "properties": {}}::edge + {"id": 1407374883553283, "label": "e1", "end_id": 281474976710661, "start_id": 281474976710660, "properties": {}}::edge +(6 rows) + +-- check reuse of 'a' +SELECT * FROM cypher('cypher_match', $$ + MATCH (a {age:4}) RETURN a $$) as (a agtype); + a +------------------------------------------------------------------------------------------ + {"id": 281474976710665, "label": "", "properties": {"age": 4}}::vertex + {"id": 281474976710659, "label": "", "properties": {"age": 4, "name": "orphan"}}::vertex + {"id": 281474976710660, "label": "", "properties": {"age": 4, "name": "F"}}::vertex + {"id": 281474976710661, "label": "", "properties": {"age": 4, "name": "T"}}::vertex +(4 rows) + +SELECT * FROM cypher('cypher_match', $$ + MATCH (a) MATCH (a {age:4}) RETURN a $$) as (a agtype); + a +------------------------------------------------------------------------------------------ + {"id": 281474976710665, "label": "", "properties": {"age": 4}}::vertex + {"id": 281474976710659, "label": "", "properties": {"age": 4, "name": "orphan"}}::vertex + {"id": 281474976710660, "label": "", "properties": {"age": 4, "name": "F"}}::vertex + {"id": 281474976710661, "label": "", "properties": {"age": 4, "name": "T"}}::vertex +(4 rows) + +SELECT * FROM cypher('cypher_match', $$ + MATCH (a {age:4, name: "orphan"}) RETURN a $$) as (a agtype); + a +------------------------------------------------------------------------------------------ + {"id": 281474976710659, "label": "", "properties": {"age": 4, "name": "orphan"}}::vertex +(1 row) + +SELECT * FROM cypher('cypher_match', $$ + MATCH (a) MATCH (a {age:4}) MATCH (a {name: "orphan"}) RETURN a $$) as (a agtype); + a +------------------------------------------------------------------------------------------ + {"id": 281474976710659, "label": "", "properties": {"age": 4, "name": "orphan"}}::vertex +(1 row) + +SELECT * FROM cypher('cypher_match', $$ + MATCH (a {age:4}) MATCH (a {name: "orphan"}) RETURN a $$) as (a agtype); + a +------------------------------------------------------------------------------------------ + {"id": 281474976710659, "label": "", "properties": {"age": 4, "name": "orphan"}}::vertex +(1 row) + +SELECT * FROM cypher('cypher_match', $$ + MATCH (a) MATCH (a {age:4}) MATCH (a {name: "orphan"}) SET a.age = 3 RETURN a $$) as (a agtype); + a +------------------------------------------------------------------------------------------ + {"id": 281474976710659, "label": "", "properties": {"age": 3, "name": "orphan"}}::vertex +(1 row) + +SELECT * FROM cypher('cypher_match', $$ + MATCH (a) MATCH (a {age:3}) MATCH (a {name: "orphan"}) RETURN a $$) as (a agtype); + a +------------------------------------------------------------------------------------------ + {"id": 281474976710659, "label": "", "properties": {"age": 3, "name": "orphan"}}::vertex +(1 row) + +SELECT * FROM cypher('cypher_match', $$ + MATCH (a {name: "orphan"}) MATCH (a {age:3}) RETURN a $$) as (a agtype); + a +------------------------------------------------------------------------------------------ + {"id": 281474976710659, "label": "", "properties": {"age": 3, "name": "orphan"}}::vertex +(1 row) + +SELECT * FROM cypher('cypher_match', $$ + MATCH (a) WHERE exists(a.age) AND exists(a.name) RETURN a $$) as (a agtype); + a +------------------------------------------------------------------------------------------ + {"id": 281474976710660, "label": "", "properties": {"age": 4, "name": "F"}}::vertex + {"id": 281474976710661, "label": "", "properties": {"age": 4, "name": "T"}}::vertex + {"id": 281474976710659, "label": "", "properties": {"age": 3, "name": "orphan"}}::vertex +(3 rows) + +SELECT * FROM cypher('cypher_match', $$ + MATCH (a) WHERE exists(a.age) AND NOT exists(a.name) RETURN a $$) as (a agtype); + a +------------------------------------------------------------------------ + {"id": 281474976710665, "label": "", "properties": {"age": 4}}::vertex + {"id": 281474976710666, "label": "", "properties": {"age": 6}}::vertex +(2 rows) + +-- check reuse of 'r' +SELECT * FROM cypher('cypher_match', $$ + MATCH ()-[r]-() RETURN r $$) as (r agtype); + r +----------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"id": 4785074604081153, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710661, "properties": {"years": 3, "relationship": "friends"}}::edge + {"id": 4785074604081153, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710661, "properties": {"years": 3, "relationship": "friends"}}::edge + {"id": 4785074604081154, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710659, "properties": {"years": 4, "relationship": "enemies"}}::edge + {"id": 4785074604081154, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710659, "properties": {"years": 4, "relationship": "enemies"}}::edge + {"id": 1407374883553283, "label": "e1", "end_id": 281474976710661, "start_id": 281474976710660, "properties": {}}::edge + {"id": 1407374883553283, "label": "e1", "end_id": 281474976710661, "start_id": 281474976710660, "properties": {}}::edge +(6 rows) + +SELECT * FROM cypher('cypher_match', $$ + MATCH ()-[r]-() MATCH ()-[r {relationship: "friends"}]-() RETURN r $$) as (r agtype); + r +----------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"id": 4785074604081153, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710661, "properties": {"years": 3, "relationship": "friends"}}::edge + {"id": 4785074604081153, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710661, "properties": {"years": 3, "relationship": "friends"}}::edge +(2 rows) + +SELECT * FROM cypher('cypher_match', $$ + MATCH ()-[r {years:3, relationship: "friends"}]-() RETURN r $$) as (r agtype); + r +----------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"id": 4785074604081153, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710661, "properties": {"years": 3, "relationship": "friends"}}::edge + {"id": 4785074604081153, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710661, "properties": {"years": 3, "relationship": "friends"}}::edge +(2 rows) + +SELECT * FROM cypher('cypher_match', $$ + MATCH ()-[r {years:3}]-() MATCH ()-[r {relationship: "friends"}]-() RETURN r $$) as (r agtype); + r +----------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"id": 4785074604081153, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710661, "properties": {"years": 3, "relationship": "friends"}}::edge + {"id": 4785074604081153, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710661, "properties": {"years": 3, "relationship": "friends"}}::edge +(2 rows) + +--mismatch year #, should return nothing +SELECT * FROM cypher('cypher_match', $$ + MATCH ()-[r {years:2}]-() MATCH ()-[r {relationship: "friends"}]-() RETURN r $$) as (r agtype); + r +--- +(0 rows) + +SELECT * FROM cypher('cypher_match', $$ + MATCH ()-[r {relationship:"enemies"}]-() MATCH ()-[r {years:4}]-() RETURN r $$) as (r agtype); + r +----------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"id": 4785074604081154, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710659, "properties": {"years": 4, "relationship": "enemies"}}::edge + {"id": 4785074604081154, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710659, "properties": {"years": 4, "relationship": "enemies"}}::edge +(2 rows) + +SELECT * FROM cypher('cypher_match', $$ + MATCH ()-[r {relationship:"enemies"}]-() MATCH ()-[r {relationship:"friends"}]-() RETURN r $$) as (r agtype); + r +--- +(0 rows) + -- -- Clean up -- SELECT drop_graph('cypher_match', true); -NOTICE: drop cascades to 16 other objects +NOTICE: drop cascades to 17 other objects DETAIL: drop cascades to table cypher_match._ag_label_vertex drop cascades to table cypher_match._ag_label_edge drop cascades to table cypher_match.v @@ -1471,6 +1693,7 @@ drop cascades to table cypher_match.dup_edge drop cascades to table cypher_match.other_v drop cascades to table cypher_match.opt_match_v drop cascades to table cypher_match.opt_match_e +drop cascades to table cypher_match.knows NOTICE: graph "cypher_match" has been dropped drop_graph ------------ diff --git a/regress/expected/cypher_merge.out b/regress/expected/cypher_merge.out index 389c34d10..988dff3c2 100644 --- a/regress/expected/cypher_merge.out +++ b/regress/expected/cypher_merge.out @@ -513,15 +513,17 @@ SELECT * FROM cypher('cypher_merge', $$MATCH (n) DETACH DELETE n $$) AS (a agtyp */ --test query SELECT * FROM cypher('cypher_merge', $$CREATE (n {i : 1}) SET n.i = 2 MERGE ({i: 2}) $$) AS (a agtype); -ERROR: missing FROM-clause entry for table "_age_default_alias_0" -LINE 5: SELECT * FROM cypher('cypher_merge', $$CREATE (n {i : 1}) SE... - ^ ---validate created correctly -SELECT * FROM cypher('cypher_merge', $$MATCH (a) RETURN a$$) AS (a agtype); a --- (0 rows) +--validate created correctly +SELECT * FROM cypher('cypher_merge', $$MATCH (a) RETURN a$$) AS (a agtype); + a +---------------------------------------------------------------------- + {"id": 281474976710690, "label": "", "properties": {"i": 2}}::vertex +(1 row) + --clean up SELECT * FROM cypher('cypher_merge', $$MATCH (n) DETACH DELETE n $$) AS (a agtype); a @@ -541,7 +543,7 @@ SELECT * FROM cypher('cypher_merge', $$CREATE (n {i : 1}) SET n.i = 2 WITH n as SELECT * FROM cypher('cypher_merge', $$MATCH (a) RETURN a$$) AS (a agtype); a ---------------------------------------------------------------------- - {"id": 281474976710690, "label": "", "properties": {"i": 2}}::vertex + {"id": 281474976710691, "label": "", "properties": {"i": 2}}::vertex (1 row) --clean up @@ -569,7 +571,7 @@ SELECT * FROM cypher('cypher_merge', $$MATCH (n {i : 1}) SET n.i = 2 WITH n as a SELECT * FROM cypher('cypher_merge', $$MATCH (a) RETURN a$$) AS (a agtype); a ---------------------------------------------------------------------- - {"id": 281474976710691, "label": "", "properties": {"i": 2}}::vertex + {"id": 281474976710692, "label": "", "properties": {"i": 2}}::vertex (1 row) --clean up @@ -594,7 +596,7 @@ ERROR: vertex assigned to variable n was deleted SELECT * FROM cypher('cypher_merge', $$MATCH (a) RETURN a$$) AS (a agtype); a ---------------------------------------------------------------------- - {"id": 281474976710692, "label": "", "properties": {"i": 1}}::vertex + {"id": 281474976710693, "label": "", "properties": {"i": 1}}::vertex (1 row) --clean up @@ -672,7 +674,7 @@ SELECT * FROM cypher('cypher_merge', $$MERGE ()-[:e]-()$$) AS (a agtype); SELECT * FROM cypher('cypher_merge', $$MATCH p=()-[]->() RETURN p$$) AS (a agtype); a --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 281474976710693, "label": "", "properties": {}}::vertex, {"id": 844424930131982, "label": "e", "end_id": 281474976710694, "start_id": 281474976710693, "properties": {}}::edge, {"id": 281474976710694, "label": "", "properties": {}}::vertex]::path + [{"id": 281474976710694, "label": "", "properties": {}}::vertex, {"id": 844424930131982, "label": "e", "end_id": 281474976710695, "start_id": 281474976710694, "properties": {}}::edge, {"id": 281474976710695, "label": "", "properties": {}}::vertex]::path (1 row) --clean up @@ -687,14 +689,14 @@ SELECT * FROM cypher('cypher_merge', $$MATCH (n) DETACH DELETE n $$) AS (a agtyp SELECT * FROM cypher('cypher_merge', $$MERGE (a) RETURN a$$) AS (a agtype); a ---------------------------------------------------------------- - {"id": 281474976710695, "label": "", "properties": {}}::vertex + {"id": 281474976710696, "label": "", "properties": {}}::vertex (1 row) --validate SELECT * FROM cypher('cypher_merge', $$MATCH (a) RETURN a$$) AS (a agtype); a ---------------------------------------------------------------- - {"id": 281474976710695, "label": "", "properties": {}}::vertex + {"id": 281474976710696, "label": "", "properties": {}}::vertex (1 row) --clean up @@ -709,14 +711,14 @@ SELECT * FROM cypher('cypher_merge', $$MATCH (n) DETACH DELETE n $$) AS (a agtyp SELECT * FROM cypher('cypher_merge', $$MERGE p=()-[:e]-() RETURN p$$) AS (a agtype); a --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 281474976710696, "label": "", "properties": {}}::vertex, {"id": 844424930131983, "label": "e", "end_id": 281474976710697, "start_id": 281474976710696, "properties": {}}::edge, {"id": 281474976710697, "label": "", "properties": {}}::vertex]::path + [{"id": 281474976710697, "label": "", "properties": {}}::vertex, {"id": 844424930131983, "label": "e", "end_id": 281474976710698, "start_id": 281474976710697, "properties": {}}::edge, {"id": 281474976710698, "label": "", "properties": {}}::vertex]::path (1 row) --validate SELECT * FROM cypher('cypher_merge', $$MATCH p=()-[]->() RETURN p$$) AS (a agtype); a --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 281474976710696, "label": "", "properties": {}}::vertex, {"id": 844424930131983, "label": "e", "end_id": 281474976710697, "start_id": 281474976710696, "properties": {}}::edge, {"id": 281474976710697, "label": "", "properties": {}}::vertex]::path + [{"id": 281474976710697, "label": "", "properties": {}}::vertex, {"id": 844424930131983, "label": "e", "end_id": 281474976710698, "start_id": 281474976710697, "properties": {}}::edge, {"id": 281474976710698, "label": "", "properties": {}}::vertex]::path (1 row) --clean up @@ -731,14 +733,14 @@ SELECT * FROM cypher('cypher_merge', $$MATCH (n) DETACH DELETE n $$) AS (a agtyp SELECT * FROM cypher('cypher_merge', $$MERGE (a)-[:e]-(b) RETURN a$$) AS (a agtype); a ---------------------------------------------------------------- - {"id": 281474976710698, "label": "", "properties": {}}::vertex + {"id": 281474976710699, "label": "", "properties": {}}::vertex (1 row) --validate SELECT * FROM cypher('cypher_merge', $$MATCH p=()-[]->() RETURN p$$) AS (a agtype); a --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 281474976710698, "label": "", "properties": {}}::vertex, {"id": 844424930131984, "label": "e", "end_id": 281474976710699, "start_id": 281474976710698, "properties": {}}::edge, {"id": 281474976710699, "label": "", "properties": {}}::vertex]::path + [{"id": 281474976710699, "label": "", "properties": {}}::vertex, {"id": 844424930131984, "label": "e", "end_id": 281474976710700, "start_id": 281474976710699, "properties": {}}::edge, {"id": 281474976710700, "label": "", "properties": {}}::vertex]::path (1 row) --clean up @@ -753,14 +755,14 @@ SELECT * FROM cypher('cypher_merge', $$MATCH (n) DETACH DELETE n $$) AS (a agtyp SELECT * FROM cypher('cypher_merge', $$CREATE p=()-[:e]->() RETURN p$$) AS (a agtype); a --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 281474976710700, "label": "", "properties": {}}::vertex, {"id": 844424930131985, "label": "e", "end_id": 281474976710701, "start_id": 281474976710700, "properties": {}}::edge, {"id": 281474976710701, "label": "", "properties": {}}::vertex]::path + [{"id": 281474976710701, "label": "", "properties": {}}::vertex, {"id": 844424930131985, "label": "e", "end_id": 281474976710702, "start_id": 281474976710701, "properties": {}}::edge, {"id": 281474976710702, "label": "", "properties": {}}::vertex]::path (1 row) SELECT * FROM cypher('cypher_merge', $$MERGE p=()-[:e]-() RETURN p$$) AS (a agtype); a --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - [{"id": 281474976710700, "label": "", "properties": {}}::vertex, {"id": 844424930131985, "label": "e", "end_id": 281474976710701, "start_id": 281474976710700, "properties": {}}::edge, {"id": 281474976710701, "label": "", "properties": {}}::vertex]::path - [{"id": 281474976710701, "label": "", "properties": {}}::vertex, {"id": 844424930131985, "label": "e", "end_id": 281474976710701, "start_id": 281474976710700, "properties": {}}::edge, {"id": 281474976710700, "label": "", "properties": {}}::vertex]::path + [{"id": 281474976710701, "label": "", "properties": {}}::vertex, {"id": 844424930131985, "label": "e", "end_id": 281474976710702, "start_id": 281474976710701, "properties": {}}::edge, {"id": 281474976710702, "label": "", "properties": {}}::vertex]::path + [{"id": 281474976710702, "label": "", "properties": {}}::vertex, {"id": 844424930131985, "label": "e", "end_id": 281474976710702, "start_id": 281474976710701, "properties": {}}::edge, {"id": 281474976710701, "label": "", "properties": {}}::vertex]::path (2 rows) --clean up @@ -805,7 +807,7 @@ ERROR: Existing variable m cannot be NULL in MERGE clause SELECT * FROM cypher('cypher_merge', $$MATCH (n) RETURN n$$) AS (a agtype); a ---------------------------------------------------------------- - {"id": 281474976710702, "label": "", "properties": {}}::vertex + {"id": 281474976710703, "label": "", "properties": {}}::vertex (1 row) -- diff --git a/regress/sql/cypher_match.sql b/regress/sql/cypher_match.sql index 344453e00..f08f8dfb0 100644 --- a/regress/sql/cypher_match.sql +++ b/regress/sql/cypher_match.sql @@ -734,6 +734,79 @@ SELECT * FROM cypher('cypher_match', $$ RETURN n $$) as (n agtype); +-- +-- Regression tests to check previous clause variable refs +-- +-- set up initial state and show what we're working with +SELECT * FROM cypher('cypher_match', $$ + CREATE (a {age: 4}) RETURN a $$) as (a agtype); +SELECT * FROM cypher('cypher_match', $$ + CREATE (b {age: 6}) RETURN b $$) as (b agtype); + +SELECT * FROM cypher('cypher_match', $$ + MATCH (a) RETURN a $$) as (a agtype); +SELECT * FROM cypher('cypher_match', $$ + MATCH (a) WHERE exists(a.name) RETURN a $$) as (a agtype); +SELECT * FROM cypher('cypher_match', $$ + MATCH (a) WHERE exists(a.name) SET a.age = 4 RETURN a $$) as (a agtype); + + +SELECT * FROM cypher('cypher_match', $$ + MATCH (a),(b) WHERE a.age = 4 AND a.name = "T" AND b.age = 6 + RETURN a,b $$) as (a agtype, b agtype); +SELECT * FROM cypher('cypher_match', $$ + MATCH (a),(b) WHERE a.age = 4 AND a.name = "T" AND b.age = 6 CREATE + (a)-[:knows {relationship: "friends", years: 3}]->(b) $$) as (r agtype); +SELECT * FROM cypher('cypher_match', $$ + MATCH (a),(b) WHERE a.age = 4 AND a.name = "orphan" AND b.age = 6 CREATE + (a)-[:knows {relationship: "enemies", years: 4}]->(b) $$) as (r agtype); +SELECT * FROM cypher('cypher_match', $$ + MATCH (a)-[r]-(b) RETURN r $$) as (r agtype); + +-- check reuse of 'a' +SELECT * FROM cypher('cypher_match', $$ + MATCH (a {age:4}) RETURN a $$) as (a agtype); +SELECT * FROM cypher('cypher_match', $$ + MATCH (a) MATCH (a {age:4}) RETURN a $$) as (a agtype); + +SELECT * FROM cypher('cypher_match', $$ + MATCH (a {age:4, name: "orphan"}) RETURN a $$) as (a agtype); +SELECT * FROM cypher('cypher_match', $$ + MATCH (a) MATCH (a {age:4}) MATCH (a {name: "orphan"}) RETURN a $$) as (a agtype); +SELECT * FROM cypher('cypher_match', $$ + MATCH (a {age:4}) MATCH (a {name: "orphan"}) RETURN a $$) as (a agtype); + +SELECT * FROM cypher('cypher_match', $$ + MATCH (a) MATCH (a {age:4}) MATCH (a {name: "orphan"}) SET a.age = 3 RETURN a $$) as (a agtype); + +SELECT * FROM cypher('cypher_match', $$ + MATCH (a) MATCH (a {age:3}) MATCH (a {name: "orphan"}) RETURN a $$) as (a agtype); +SELECT * FROM cypher('cypher_match', $$ + MATCH (a {name: "orphan"}) MATCH (a {age:3}) RETURN a $$) as (a agtype); + +SELECT * FROM cypher('cypher_match', $$ + MATCH (a) WHERE exists(a.age) AND exists(a.name) RETURN a $$) as (a agtype); + +SELECT * FROM cypher('cypher_match', $$ + MATCH (a) WHERE exists(a.age) AND NOT exists(a.name) RETURN a $$) as (a agtype); + +-- check reuse of 'r' +SELECT * FROM cypher('cypher_match', $$ + MATCH ()-[r]-() RETURN r $$) as (r agtype); +SELECT * FROM cypher('cypher_match', $$ + MATCH ()-[r]-() MATCH ()-[r {relationship: "friends"}]-() RETURN r $$) as (r agtype); +SELECT * FROM cypher('cypher_match', $$ + MATCH ()-[r {years:3, relationship: "friends"}]-() RETURN r $$) as (r agtype); +SELECT * FROM cypher('cypher_match', $$ + MATCH ()-[r {years:3}]-() MATCH ()-[r {relationship: "friends"}]-() RETURN r $$) as (r agtype); +--mismatch year #, should return nothing +SELECT * FROM cypher('cypher_match', $$ + MATCH ()-[r {years:2}]-() MATCH ()-[r {relationship: "friends"}]-() RETURN r $$) as (r agtype); +SELECT * FROM cypher('cypher_match', $$ + MATCH ()-[r {relationship:"enemies"}]-() MATCH ()-[r {years:4}]-() RETURN r $$) as (r agtype); +SELECT * FROM cypher('cypher_match', $$ + MATCH ()-[r {relationship:"enemies"}]-() MATCH ()-[r {relationship:"friends"}]-() RETURN r $$) as (r agtype); + -- -- Clean up -- diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 526a23fbf..01b336057 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -163,7 +163,8 @@ static A_Expr *filter_vertices_on_label_id(cypher_parsestate *cpstate, Node *id_field, char *label); static Node *create_property_constraints(cypher_parsestate *cpstate, transform_entity *entity, - Node *property_constraints); + Node *property_constraints, + Node *prop_expr); static TargetEntry *findTarget(List *targetList, char *resname); static transform_entity *transform_VLE_edge_entity(cypher_parsestate *cpstate, cypher_relationship *rel, @@ -3497,33 +3498,43 @@ static A_Expr *filter_vertices_on_label_id(cypher_parsestate *cpstate, */ static Node *create_property_constraints(cypher_parsestate *cpstate, transform_entity *entity, - Node *property_constraints) + Node *property_constraints, + Node *prop_expr) { ParseState *pstate = (ParseState *)cpstate; char *entity_name; - ColumnRef *cr; - Node *prop_expr, *const_expr; + Node *const_expr; RangeTblEntry *rte; Node *last_srf = pstate->p_last_srf; - cr = makeNode(ColumnRef); - - entity_name = get_entity_name(entity); + /* + * If the prop_expr node wasn't passed in, create it. Otherwise, skip + * the creation step. + */ + if (prop_expr == NULL) + { + ColumnRef *cr = NULL; - cr->fields = list_make2(makeString(entity_name), makeString("properties")); + cr = makeNode(ColumnRef); + entity_name = get_entity_name(entity); + 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 Postgres to get the properties' transform node */ + rte = find_rte(cpstate, entity_name); + if (rte != NULL) + { + 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 + /* use cypher to get the constraints' transform node */ const_expr = transform_cypher_expr(cpstate, property_constraints, EXPR_KIND_WHERE); @@ -3808,13 +3819,68 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, entity = make_transform_entity(cpstate, ENT_VERTEX, (Node *)node, expr); - /* transform properties if they exist */ + /* transform the properties if they exist */ if (node->props) { Node *n = NULL; + Node *prop_var = NULL; + Node *prop_expr = NULL; + + /* + * We need to build a transformed properties(prop_var) + * expression IF the properties variable already exists from a + * previous clause. Please note that the "found" prop_var was + * previously transformed. + */ + + /* get the prop_var if it was previously resolved */ + if (node->name != NULL) + { + prop_var = colNameToVar(pstate, node->name, false, + node->location); + } + + /* + * If prop_var exists and is an alias, just pass it through by + * assigning the prop_expr the prop_var. + */ + if (prop_var != NULL && + pg_strncasecmp(node->name, AGE_DEFAULT_ALIAS_PREFIX, + strlen(AGE_DEFAULT_ALIAS_PREFIX)) == 0) + { + prop_expr = prop_var; + } + /* + * Else, if it exists and is not an alias, create the prop_expr + * as a transformed properties(prop_var) function node. + */ + else if (prop_var != NULL) + { + /* + * Remember that prop_var is already transformed. We need + * to built the transform manually. + */ + FuncCall *fc = NULL; + List *targs = NIL; + List *fname = NIL; + + targs = lappend(targs, prop_var); + fname = list_make2(makeString("ag_catalog"), + makeString("age_properties")); + fc = makeFuncCall(fname, targs, -1); + + /* + * Hand off to ParseFuncOrColumn to create the function + * expression for properties(prop_var) + */ + prop_expr = ParseFuncOrColumn(pstate, fname, targs, + pstate->p_last_srf, fc, false, + -1); + } ((cypher_map*)node->props)->keep_null = true; - n = create_property_constraints(cpstate, entity, node->props); + n = create_property_constraints(cpstate, entity, node->props, + prop_expr); cpstate->property_constraint_quals = lappend(cpstate->property_constraint_quals, n); @@ -3873,13 +3939,66 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, if (rel->props) { - Node *n; + Node *r = NULL; + Node *prop_var = NULL; + Node *prop_expr = NULL; + + /* + * We need to build a transformed properties(prop_var) + * expression IF the properties variable already exists from + * a previous clause. Please note that the "found" prop_var + * was previously transformed. + */ + + /* get the prop_var if it was previously resolved */ + if (rel->name != NULL) + { + prop_var = colNameToVar(pstate, rel->name, false, + rel->location); + } + + /* + * If prop_var exists and is an alias, just pass it through by + * assigning the prop_expr the prop_var. + */ + if (prop_var != NULL && + pg_strncasecmp(rel->name, AGE_DEFAULT_ALIAS_PREFIX, + strlen(AGE_DEFAULT_ALIAS_PREFIX)) == 0) + { + prop_expr = prop_var; + } + /* + * Else, if it exists and is not an alias, create the prop_expr + * as a transformed properties(prop_var) function node. + */ + else if (prop_var != NULL) + { + /* + * Remember that prop_var is already transformed. We need + * to built the transform manually. + */ + FuncCall *fc = NULL; + List *targs = NIL; + List *fname = NIL; + + targs = lappend(targs, prop_var); + fname = list_make2(makeString("ag_catalog"), + makeString("age_properties")); + fc = makeFuncCall(fname, targs, -1); + + /* + * Hand off to ParseFuncOrColumn to create the function + * expression for properties(prop_var) + */ + prop_expr = ParseFuncOrColumn(pstate, fname, targs, + pstate->p_last_srf, fc, + false, -1); + } ((cypher_map*)rel->props)->keep_null = true; - n = create_property_constraints(cpstate, entity, - rel->props); + r = create_property_constraints(cpstate, entity, rel->props, prop_expr); cpstate->property_constraint_quals = - lappend(cpstate->property_constraint_quals, n); + lappend(cpstate->property_constraint_quals, r); } entities = lappend(entities, entity);