This is an automated email from the ASF dual-hosted git repository.
jgemignani pushed a commit to branch PG12
in repository https://gitbox.apache.org/repos/asf/age.git
The following commit(s) were added to refs/heads/PG12 by this push:
new 078c74d5 Implement EXISTS Subquery (#1655)
078c74d5 is described below
commit 078c74d5a748271c15361fca5b0327e8ac745ddb
Author: Dehowe Feng <[email protected]>
AuthorDate: Tue Mar 12 03:34:40 2024 +0800
Implement EXISTS Subquery (#1655)
* Add grammar rules for exists subquery
Add grammar rule for exists subquery. EXISTS subquery can be used
with UNION and does not have to feature a RETURN.
Regression tests not yet added.
* Implement EXISTS Subquery
Implements a naive version of EXISTS subquery that evaluates the
underlying subquery.
Add regression tests related to EXISTS
TODO: Implement logic to allow the underlying subquery to constrain
the queries in the outer scope.
Also fixes a minor typo.
---
Makefile | 1 +
regress/expected/cypher_subquery.out | 360 +++++++++++++++++++++++++++++++++
regress/sql/cypher_subquery.sql | 201 ++++++++++++++++++
src/backend/nodes/ag_nodes.c | 2 +
src/backend/nodes/cypher_outfuncs.c | 9 +
src/backend/parser/cypher_analyze.c | 1 +
src/backend/parser/cypher_clause.c | 84 +++++++-
src/backend/parser/cypher_gram.y | 137 +++++++++++++
src/backend/parser/cypher_parse_node.c | 3 +-
src/include/nodes/ag_nodes.h | 3 +-
src/include/nodes/cypher_nodes.h | 9 +-
src/include/nodes/cypher_outfuncs.h | 3 +-
src/include/parser/cypher_parse_node.h | 1 +
13 files changed, 808 insertions(+), 6 deletions(-)
diff --git a/Makefile b/Makefile
index b405ff61..957c9a61 100644
--- a/Makefile
+++ b/Makefile
@@ -102,6 +102,7 @@ REGRESS = scan \
cypher_union \
cypher_call \
cypher_merge \
+ cypher_subquery \
age_global_graph \
age_load \
index \
diff --git a/regress/expected/cypher_subquery.out
b/regress/expected/cypher_subquery.out
new file mode 100644
index 00000000..1b21319b
--- /dev/null
+++ b/regress/expected/cypher_subquery.out
@@ -0,0 +1,360 @@
+LOAD 'age';
+SET search_path TO ag_catalog;
+SELECT * FROM create_graph('exists_subquery');
+NOTICE: graph "exists_subquery" has been created
+ create_graph
+--------------
+
+(1 row)
+
+SELECT * FROM cypher('exists_subquery', $$
+
CREATE (:person {name: "Briggite", age: 32})-[:knows]->(:person {name:
"Takeshi", age: 28}),
+
(:person {name: "Faye", age: 25})-[:knows]->(:person {name: "Tony",
age: 34})-[:loved]->(:person {name : "Valerie", age: 33}),
+
(:person {name: "Calvin", age: 6})-[:knows]->(:pet {name: "Hobbes"}),
+
(:person {name: "Charlie", age: 8})-[:knows]->(:pet {name :
"Snoopy"})
+
$$) AS (result agtype);
+ result
+--------
+(0 rows)
+
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a) RETURN (a) $$) AS (result
agtype);
+ result
+---------------------------------------------------------------------------------------------------
+ {"id": 844424930131969, "label": "person", "properties": {"age": 32, "name":
"Briggite"}}::vertex
+ {"id": 844424930131970, "label": "person", "properties": {"age": 28, "name":
"Takeshi"}}::vertex
+ {"id": 844424930131971, "label": "person", "properties": {"age": 25, "name":
"Faye"}}::vertex
+ {"id": 844424930131972, "label": "person", "properties": {"age": 34, "name":
"Tony"}}::vertex
+ {"id": 844424930131973, "label": "person", "properties": {"age": 33, "name":
"Valerie"}}::vertex
+ {"id": 844424930131974, "label": "person", "properties": {"age": 6, "name":
"Calvin"}}::vertex
+ {"id": 844424930131975, "label": "person", "properties": {"age": 8, "name":
"Charlie"}}::vertex
+ {"id": 1688849860263937, "label": "pet", "properties": {"name":
"Hobbes"}}::vertex
+ {"id": 1688849860263938, "label": "pet", "properties": {"name":
"Snoopy"}}::vertex
+(9 rows)
+
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {(a:person)-[]->(:pet)}
+
RETURN (a) $$) AS (result agtype);
+ result
+-------------------------------------------------------------------------------------------------
+ {"id": 844424930131974, "label": "person", "properties": {"age": 6, "name":
"Calvin"}}::vertex
+ {"id": 844424930131975, "label": "person", "properties": {"age": 8, "name":
"Charlie"}}::vertex
+(2 rows)
+
+--trying to use b when not defined, should fail
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {(a:person)-[]->(b:pet)}
+
RETURN (a) $$) AS (result agtype);
+ERROR: variable `b` does not exist
+LINE 2: WHERE EXISTS {(a:person)-[]->(b:pet)}
+ ^
+--query inside
+--EXISTS subquery is currently implemented naively, without constraints in the
+--subquery. the results of this regression test may change upon implementation
+--TODO: implement inner subquery constraints
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {MATCH (a:person)-[]->(b:pet) RETURN b}
+
RETURN (a) $$) AS (result agtype);
+ result
+---------------------------------------------------------------------------------------------------
+ {"id": 844424930131969, "label": "person", "properties": {"age": 32, "name":
"Briggite"}}::vertex
+ {"id": 844424930131970, "label": "person", "properties": {"age": 28, "name":
"Takeshi"}}::vertex
+ {"id": 844424930131971, "label": "person", "properties": {"age": 25, "name":
"Faye"}}::vertex
+ {"id": 844424930131972, "label": "person", "properties": {"age": 34, "name":
"Tony"}}::vertex
+ {"id": 844424930131973, "label": "person", "properties": {"age": 33, "name":
"Valerie"}}::vertex
+ {"id": 844424930131974, "label": "person", "properties": {"age": 6, "name":
"Calvin"}}::vertex
+ {"id": 844424930131975, "label": "person", "properties": {"age": 8, "name":
"Charlie"}}::vertex
+(7 rows)
+
+--repeat variable in match
+--EXISTS subquery is currently implemented naively, without constraints in the
+--subquery. the results of this regression test may change upon implementation
+--TODO: implement inner subquery constraints
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {
+
MATCH (a:person)
+
WHERE a.name = 'Takeshi'
+
RETURN a
+
}
+
RETURN (a) $$) AS (result agtype);
+ result
+---------------------------------------------------------------------------------------------------
+ {"id": 844424930131969, "label": "person", "properties": {"age": 32, "name":
"Briggite"}}::vertex
+ {"id": 844424930131970, "label": "person", "properties": {"age": 28, "name":
"Takeshi"}}::vertex
+ {"id": 844424930131971, "label": "person", "properties": {"age": 25, "name":
"Faye"}}::vertex
+ {"id": 844424930131972, "label": "person", "properties": {"age": 34, "name":
"Tony"}}::vertex
+ {"id": 844424930131973, "label": "person", "properties": {"age": 33, "name":
"Valerie"}}::vertex
+ {"id": 844424930131974, "label": "person", "properties": {"age": 6, "name":
"Calvin"}}::vertex
+ {"id": 844424930131975, "label": "person", "properties": {"age": 8, "name":
"Charlie"}}::vertex
+(7 rows)
+
+--query inside, with WHERE
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {MATCH (a:person)-[]->(b:pet)
+
WHERE b.name = 'Briggite'
+
RETURN b}
+
RETURN (a) $$) AS (result agtype);
+ result
+--------
+(0 rows)
+
+--no return
+--EXISTS subquery is currently implemented naively, without constraints in the
+--subquery. the results of this regression test may change upon implementation
+--TODO: implement inner subquery constraints
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {MATCH (a:person)-[]->(b:pet)
+
WHERE a.name = 'Calvin'}
+
RETURN (a) $$) AS (result agtype);
+ result
+---------------------------------------------------------------------------------------------------
+ {"id": 844424930131969, "label": "person", "properties": {"age": 32, "name":
"Briggite"}}::vertex
+ {"id": 844424930131970, "label": "person", "properties": {"age": 28, "name":
"Takeshi"}}::vertex
+ {"id": 844424930131971, "label": "person", "properties": {"age": 25, "name":
"Faye"}}::vertex
+ {"id": 844424930131972, "label": "person", "properties": {"age": 34, "name":
"Tony"}}::vertex
+ {"id": 844424930131973, "label": "person", "properties": {"age": 33, "name":
"Valerie"}}::vertex
+ {"id": 844424930131974, "label": "person", "properties": {"age": 6, "name":
"Calvin"}}::vertex
+ {"id": 844424930131975, "label": "person", "properties": {"age": 8, "name":
"Charlie"}}::vertex
+(7 rows)
+
+--union
+--EXISTS subquery is currently implemented naively, without constraints in the
+--subquery. the results of this regression test may change upon implementation
+--TODO: implement inner subquery constraints
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {
+
MATCH (a:person)-[]->(b:pet)
+
WHERE b.name = 'Hobbes'
+
RETURN b
+
UNION
+
MATCH (c:person)-[]->(d:person)
+
RETURN c
+
}
+
RETURN (a) $$) AS (result agtype);
+ result
+---------------------------------------------------------------------------------------------------
+ {"id": 844424930131969, "label": "person", "properties": {"age": 32, "name":
"Briggite"}}::vertex
+ {"id": 844424930131970, "label": "person", "properties": {"age": 28, "name":
"Takeshi"}}::vertex
+ {"id": 844424930131971, "label": "person", "properties": {"age": 25, "name":
"Faye"}}::vertex
+ {"id": 844424930131972, "label": "person", "properties": {"age": 34, "name":
"Tony"}}::vertex
+ {"id": 844424930131973, "label": "person", "properties": {"age": 33, "name":
"Valerie"}}::vertex
+ {"id": 844424930131974, "label": "person", "properties": {"age": 6, "name":
"Calvin"}}::vertex
+ {"id": 844424930131975, "label": "person", "properties": {"age": 8, "name":
"Charlie"}}::vertex
+(7 rows)
+
+-- union, mismatched var, should fail
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {
+
MATCH (a:person)-[]->(b:pet)
+
WHERE b.name = 'Snoopy'
+
RETURN c
+
UNION
+
MATCH (c:person)-[]->(d:person)
+
RETURN c
+
}
+
RETURN (a) $$) AS (result agtype);
+ERROR: could not find rte for c
+LINE 5: RETURN c
+ ^
+--union, no returns
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {
+
MATCH (a:person)-[]->(b:pet)
+
WHERE a.name = 'Charlie'
+
UNION
+
MATCH (c:person)-[]->(d:person)
+
}
+
RETURN (a) $$) AS (result agtype);
+ result
+---------------------------------------------------------------------------------------------------
+ {"id": 844424930131969, "label": "person", "properties": {"age": 32, "name":
"Briggite"}}::vertex
+ {"id": 844424930131970, "label": "person", "properties": {"age": 28, "name":
"Takeshi"}}::vertex
+ {"id": 844424930131971, "label": "person", "properties": {"age": 25, "name":
"Faye"}}::vertex
+ {"id": 844424930131972, "label": "person", "properties": {"age": 34, "name":
"Tony"}}::vertex
+ {"id": 844424930131973, "label": "person", "properties": {"age": 33, "name":
"Valerie"}}::vertex
+ {"id": 844424930131974, "label": "person", "properties": {"age": 6, "name":
"Calvin"}}::vertex
+ {"id": 844424930131975, "label": "person", "properties": {"age": 8, "name":
"Charlie"}}::vertex
+(7 rows)
+
+--union, mismatched returns, should fail
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {
+
MATCH (a:person)-[]->(b:pet)
+
WHERE a.name = 'Faye'
+
RETURN a
+
UNION
+
MATCH (c:person)-[]->(d:person)
+
}
+
RETURN (a) $$) AS (result agtype);
+ERROR: syntax error at or near "}"
+LINE 8: }
+ ^
+--nesting
+--EXISTS subquery is currently implemented naively, without constraints in the
+--subquery. the results of this regression test may change upon implementation
+--TODO: implement inner subquery constraints
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {
+
MATCH (b:person)
+
WHERE EXISTS {
+
MATCH (c:person)
+
WHERE c.name =
'Takeshi'
+
RETURN c
+
}
+
}
+
RETURN (a) $$) AS (result agtype);
+ result
+---------------------------------------------------------------------------------------------------
+ {"id": 844424930131969, "label": "person", "properties": {"age": 32, "name":
"Briggite"}}::vertex
+ {"id": 844424930131970, "label": "person", "properties": {"age": 28, "name":
"Takeshi"}}::vertex
+ {"id": 844424930131971, "label": "person", "properties": {"age": 25, "name":
"Faye"}}::vertex
+ {"id": 844424930131972, "label": "person", "properties": {"age": 34, "name":
"Tony"}}::vertex
+ {"id": 844424930131973, "label": "person", "properties": {"age": 33, "name":
"Valerie"}}::vertex
+ {"id": 844424930131974, "label": "person", "properties": {"age": 6, "name":
"Calvin"}}::vertex
+ {"id": 844424930131975, "label": "person", "properties": {"age": 8, "name":
"Charlie"}}::vertex
+(7 rows)
+
+--nesting, accessing var in outer scope
+--EXISTS subquery is currently implemented naively, without constraints in the
+--subquery. the results of this regression test may change upon implementation
+--TODO: implement inner subquery constraints
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {
+
MATCH (b:person)
+
WHERE EXISTS {
+
MATCH (c:person)
+
WHERE b = c
+
RETURN c
+
}
+
}
+
RETURN (a) $$) AS (result agtype);
+ result
+---------------------------------------------------------------------------------------------------
+ {"id": 844424930131969, "label": "person", "properties": {"age": 32, "name":
"Briggite"}}::vertex
+ {"id": 844424930131970, "label": "person", "properties": {"age": 28, "name":
"Takeshi"}}::vertex
+ {"id": 844424930131971, "label": "person", "properties": {"age": 25, "name":
"Faye"}}::vertex
+ {"id": 844424930131972, "label": "person", "properties": {"age": 34, "name":
"Tony"}}::vertex
+ {"id": 844424930131973, "label": "person", "properties": {"age": 33, "name":
"Valerie"}}::vertex
+ {"id": 844424930131974, "label": "person", "properties": {"age": 6, "name":
"Calvin"}}::vertex
+ {"id": 844424930131975, "label": "person", "properties": {"age": 8, "name":
"Charlie"}}::vertex
+(7 rows)
+
+--nesting, accessing indirection in outer scope
+--EXISTS subquery is currently implemented naively, without constraints in the
+--subquery. the results of this regression test may change upon implementation
+--TODO: implement inner subquery constraints
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {
+
MATCH (b:person)
+
WHERE EXISTS {
+
MATCH (c:person)
+
WHERE b.name =
'Takeshi'
+
RETURN c
+
}
+
}
+
RETURN (a) $$) AS (result agtype);
+ result
+---------------------------------------------------------------------------------------------------
+ {"id": 844424930131969, "label": "person", "properties": {"age": 32, "name":
"Briggite"}}::vertex
+ {"id": 844424930131970, "label": "person", "properties": {"age": 28, "name":
"Takeshi"}}::vertex
+ {"id": 844424930131971, "label": "person", "properties": {"age": 25, "name":
"Faye"}}::vertex
+ {"id": 844424930131972, "label": "person", "properties": {"age": 34, "name":
"Tony"}}::vertex
+ {"id": 844424930131973, "label": "person", "properties": {"age": 33, "name":
"Valerie"}}::vertex
+ {"id": 844424930131974, "label": "person", "properties": {"age": 6, "name":
"Calvin"}}::vertex
+ {"id": 844424930131975, "label": "person", "properties": {"age": 8, "name":
"Charlie"}}::vertex
+(7 rows)
+
+--nesting, accessing var 2+ levels up
+--EXISTS subquery is currently implemented naively, without constraints in the
+--subquery. the results of this regression test may change upon implementation
+--TODO: implement inner subquery constraints
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {
+
MATCH (b:person)
+
WHERE EXISTS {
+
MATCH (c:person)
+
WHERE a.name =
'Takeshi'
+
RETURN c
+
}
+
}
+
RETURN (a) $$) AS (result agtype);
+ result
+---------------------------------------------------------------------------------------------------
+ {"id": 844424930131969, "label": "person", "properties": {"age": 32, "name":
"Briggite"}}::vertex
+ {"id": 844424930131970, "label": "person", "properties": {"age": 28, "name":
"Takeshi"}}::vertex
+ {"id": 844424930131971, "label": "person", "properties": {"age": 25, "name":
"Faye"}}::vertex
+ {"id": 844424930131972, "label": "person", "properties": {"age": 34, "name":
"Tony"}}::vertex
+ {"id": 844424930131973, "label": "person", "properties": {"age": 33, "name":
"Valerie"}}::vertex
+ {"id": 844424930131974, "label": "person", "properties": {"age": 6, "name":
"Calvin"}}::vertex
+ {"id": 844424930131975, "label": "person", "properties": {"age": 8, "name":
"Charlie"}}::vertex
+(7 rows)
+
+--nesting, accessing indirection 2+ levels up
+--EXISTS subquery is currently implemented naively, without constraints in the
+--subquery. the results of this regression test may change upon implementation
+--TODO: implement inner subquery constraints
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {
+
MATCH (b:person)
+
WHERE EXISTS {
+
MATCH (c:person)
+
WHERE a = b
+
RETURN c
+
}
+
}
+
RETURN (a) $$) AS (result agtype);
+ result
+---------------------------------------------------------------------------------------------------
+ {"id": 844424930131969, "label": "person", "properties": {"age": 32, "name":
"Briggite"}}::vertex
+ {"id": 844424930131970, "label": "person", "properties": {"age": 28, "name":
"Takeshi"}}::vertex
+ {"id": 844424930131971, "label": "person", "properties": {"age": 25, "name":
"Faye"}}::vertex
+ {"id": 844424930131972, "label": "person", "properties": {"age": 34, "name":
"Tony"}}::vertex
+ {"id": 844424930131973, "label": "person", "properties": {"age": 33, "name":
"Valerie"}}::vertex
+ {"id": 844424930131974, "label": "person", "properties": {"age": 6, "name":
"Calvin"}}::vertex
+ {"id": 844424930131975, "label": "person", "properties": {"age": 8, "name":
"Charlie"}}::vertex
+(7 rows)
+
+--EXISTS outside of WHERE
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
RETURN a, EXISTS {(a:person)-[]->(:pet)}
+
$$) AS (a agtype, exists agtype);
+ a
| exists
+---------------------------------------------------------------------------------------------------+--------
+ {"id": 844424930131969, "label": "person", "properties": {"age": 32, "name":
"Briggite"}}::vertex | false
+ {"id": 844424930131970, "label": "person", "properties": {"age": 28, "name":
"Takeshi"}}::vertex | false
+ {"id": 844424930131971, "label": "person", "properties": {"age": 25, "name":
"Faye"}}::vertex | false
+ {"id": 844424930131972, "label": "person", "properties": {"age": 34, "name":
"Tony"}}::vertex | false
+ {"id": 844424930131973, "label": "person", "properties": {"age": 33, "name":
"Valerie"}}::vertex | false
+ {"id": 844424930131974, "label": "person", "properties": {"age": 6, "name":
"Calvin"}}::vertex | true
+ {"id": 844424930131975, "label": "person", "properties": {"age": 8, "name":
"Charlie"}}::vertex | true
+(7 rows)
+
+--Var doesnt exist in outside scope, should fail
+SELECT * FROM cypher('exists_subquery', $$ RETURN 1,
+
EXISTS {
+
MATCH (b:person)-[]->(:pet)
+
RETURN a
+
}
+
$$) AS (a agtype, exists agtype);
+ERROR: could not find rte for a
+LINE 4: RETURN a
+ ^
+--
+-- Cleanup
+--
+SELECT * FROM drop_graph('exists_subquery', true);
+NOTICE: drop cascades to 6 other objects
+DETAIL: drop cascades to table exists_subquery._ag_label_vertex
+drop cascades to table exists_subquery._ag_label_edge
+drop cascades to table exists_subquery.person
+drop cascades to table exists_subquery.knows
+drop cascades to table exists_subquery.loved
+drop cascades to table exists_subquery.pet
+NOTICE: graph "exists_subquery" has been dropped
+ drop_graph
+------------
+
+(1 row)
+
+--
+-- End of tests
+--
diff --git a/regress/sql/cypher_subquery.sql b/regress/sql/cypher_subquery.sql
new file mode 100644
index 00000000..efc90295
--- /dev/null
+++ b/regress/sql/cypher_subquery.sql
@@ -0,0 +1,201 @@
+LOAD 'age';
+SET search_path TO ag_catalog;
+
+SELECT * FROM create_graph('exists_subquery');
+
+SELECT * FROM cypher('exists_subquery', $$
+
CREATE (:person {name: "Briggite", age: 32})-[:knows]->(:person {name:
"Takeshi", age: 28}),
+
(:person {name: "Faye", age: 25})-[:knows]->(:person {name: "Tony",
age: 34})-[:loved]->(:person {name : "Valerie", age: 33}),
+
(:person {name: "Calvin", age: 6})-[:knows]->(:pet {name: "Hobbes"}),
+
(:person {name: "Charlie", age: 8})-[:knows]->(:pet {name :
"Snoopy"})
+
$$) AS (result agtype);
+
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a) RETURN (a) $$) AS (result
agtype);
+
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {(a:person)-[]->(:pet)}
+
RETURN (a) $$) AS (result agtype);
+--trying to use b when not defined, should fail
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {(a:person)-[]->(b:pet)}
+
RETURN (a) $$) AS (result agtype);
+--query inside
+--EXISTS subquery is currently implemented naively, without constraints in the
+--subquery. the results of this regression test may change upon implementation
+--TODO: implement inner subquery constraints
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {MATCH (a:person)-[]->(b:pet) RETURN b}
+
RETURN (a) $$) AS (result agtype);
+
+--repeat variable in match
+--EXISTS subquery is currently implemented naively, without constraints in the
+--subquery. the results of this regression test may change upon implementation
+--TODO: implement inner subquery constraints
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {
+
MATCH (a:person)
+
WHERE a.name = 'Takeshi'
+
RETURN a
+
}
+
RETURN (a) $$) AS (result agtype);
+--query inside, with WHERE
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {MATCH (a:person)-[]->(b:pet)
+
WHERE b.name = 'Briggite'
+
RETURN b}
+
RETURN (a) $$) AS (result agtype);
+
+
+--no return
+--EXISTS subquery is currently implemented naively, without constraints in the
+--subquery. the results of this regression test may change upon implementation
+--TODO: implement inner subquery constraints
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {MATCH (a:person)-[]->(b:pet)
+
WHERE a.name = 'Calvin'}
+
RETURN (a) $$) AS (result agtype);
+
+--union
+--EXISTS subquery is currently implemented naively, without constraints in the
+--subquery. the results of this regression test may change upon implementation
+--TODO: implement inner subquery constraints
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {
+
MATCH (a:person)-[]->(b:pet)
+
WHERE b.name = 'Hobbes'
+
RETURN b
+
UNION
+
MATCH (c:person)-[]->(d:person)
+
RETURN c
+
}
+
RETURN (a) $$) AS (result agtype);
+
+-- union, mismatched var, should fail
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {
+
MATCH (a:person)-[]->(b:pet)
+
WHERE b.name = 'Snoopy'
+
RETURN c
+
UNION
+
MATCH (c:person)-[]->(d:person)
+
RETURN c
+
}
+
RETURN (a) $$) AS (result agtype);
+
+--union, no returns
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {
+
MATCH (a:person)-[]->(b:pet)
+
WHERE a.name = 'Charlie'
+
UNION
+
MATCH (c:person)-[]->(d:person)
+
}
+
RETURN (a) $$) AS (result agtype);
+
+--union, mismatched returns, should fail
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {
+
MATCH (a:person)-[]->(b:pet)
+
WHERE a.name = 'Faye'
+
RETURN a
+
UNION
+
MATCH (c:person)-[]->(d:person)
+
}
+
RETURN (a) $$) AS (result agtype);
+
+--nesting
+--EXISTS subquery is currently implemented naively, without constraints in the
+--subquery. the results of this regression test may change upon implementation
+--TODO: implement inner subquery constraints
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {
+
MATCH (b:person)
+
WHERE EXISTS {
+
MATCH (c:person)
+
WHERE c.name =
'Takeshi'
+
RETURN c
+
}
+
}
+
RETURN (a) $$) AS (result agtype);
+
+--nesting, accessing var in outer scope
+--EXISTS subquery is currently implemented naively, without constraints in the
+--subquery. the results of this regression test may change upon implementation
+--TODO: implement inner subquery constraints
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {
+
MATCH (b:person)
+
WHERE EXISTS {
+
MATCH (c:person)
+
WHERE b = c
+
RETURN c
+
}
+
}
+
RETURN (a) $$) AS (result agtype);
+
+--nesting, accessing indirection in outer scope
+--EXISTS subquery is currently implemented naively, without constraints in the
+--subquery. the results of this regression test may change upon implementation
+--TODO: implement inner subquery constraints
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {
+
MATCH (b:person)
+
WHERE EXISTS {
+
MATCH (c:person)
+
WHERE b.name =
'Takeshi'
+
RETURN c
+
}
+
}
+
RETURN (a) $$) AS (result agtype);
+
+--nesting, accessing var 2+ levels up
+--EXISTS subquery is currently implemented naively, without constraints in the
+--subquery. the results of this regression test may change upon implementation
+--TODO: implement inner subquery constraints
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {
+
MATCH (b:person)
+
WHERE EXISTS {
+
MATCH (c:person)
+
WHERE a.name =
'Takeshi'
+
RETURN c
+
}
+
}
+
RETURN (a) $$) AS (result agtype);
+
+--nesting, accessing indirection 2+ levels up
+--EXISTS subquery is currently implemented naively, without constraints in the
+--subquery. the results of this regression test may change upon implementation
+--TODO: implement inner subquery constraints
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
WHERE EXISTS {
+
MATCH (b:person)
+
WHERE EXISTS {
+
MATCH (c:person)
+
WHERE a = b
+
RETURN c
+
}
+
}
+
RETURN (a) $$) AS (result agtype);
+
+--EXISTS outside of WHERE
+SELECT * FROM cypher('exists_subquery', $$ MATCH (a:person)
+
RETURN a, EXISTS {(a:person)-[]->(:pet)}
+
$$) AS (a agtype, exists agtype);
+
+--Var doesnt exist in outside scope, should fail
+SELECT * FROM cypher('exists_subquery', $$ RETURN 1,
+
EXISTS {
+
MATCH (b:person)-[]->(:pet)
+
RETURN a
+
}
+
$$) AS (a agtype, exists agtype);
+
+--
+-- Cleanup
+--
+SELECT * FROM drop_graph('exists_subquery', true);
+
+--
+-- End of tests
+--
diff --git a/src/backend/nodes/ag_nodes.c b/src/backend/nodes/ag_nodes.c
index 73f6154a..a41cdd0b 100644
--- a/src/backend/nodes/ag_nodes.c
+++ b/src/backend/nodes/ag_nodes.c
@@ -52,6 +52,7 @@ const char *node_names[] = {
"cypher_typecast",
"cypher_integer_const",
"cypher_sub_pattern",
+ "cypher_sub_query",
"cypher_call",
"cypher_create_target_nodes",
"cypher_create_path",
@@ -117,6 +118,7 @@ const ExtensibleNodeMethods node_methods[] = {
DEFINE_NODE_METHODS(cypher_typecast),
DEFINE_NODE_METHODS(cypher_integer_const),
DEFINE_NODE_METHODS(cypher_sub_pattern),
+ DEFINE_NODE_METHODS(cypher_sub_query),
DEFINE_NODE_METHODS(cypher_call),
DEFINE_NODE_METHODS_EXTENDED(cypher_create_target_nodes),
DEFINE_NODE_METHODS_EXTENDED(cypher_create_path),
diff --git a/src/backend/nodes/cypher_outfuncs.c
b/src/backend/nodes/cypher_outfuncs.c
index 0cf8e815..0279e24d 100644
--- a/src/backend/nodes/cypher_outfuncs.c
+++ b/src/backend/nodes/cypher_outfuncs.c
@@ -320,6 +320,15 @@ void out_cypher_sub_pattern(StringInfo str, const
ExtensibleNode *node)
WRITE_NODE_FIELD(pattern);
}
+// serialization function for the cypher_sub_pattern ExtensibleNode.
+void out_cypher_sub_query(StringInfo str, const ExtensibleNode *node)
+{
+ DEFINE_AG_NODE(cypher_sub_query);
+
+ WRITE_ENUM_FIELD(kind, csp_kind);
+ WRITE_NODE_FIELD(query);
+}
+
// serialization function for the cypher_call ExtensibleNode.
void out_cypher_call(StringInfo str, const ExtensibleNode *node)
{
diff --git a/src/backend/parser/cypher_analyze.c
b/src/backend/parser/cypher_analyze.c
index 4db89242..500a97b3 100644
--- a/src/backend/parser/cypher_analyze.c
+++ b/src/backend/parser/cypher_analyze.c
@@ -711,6 +711,7 @@ static Query *analyze_cypher(List *stmt, ParseState
*parent_pstate,
cpstate->params = params;
cpstate->default_alias_num = 0;
cpstate->entities = NIL;
+ cpstate->subquery_where_flag = false;
/*
* install error context callback to adjust an error position since
* locations in stmt are 0 based
diff --git a/src/backend/parser/cypher_clause.c
b/src/backend/parser/cypher_clause.c
index 231bb64c..4bb4e7bc 100644
--- a/src/backend/parser/cypher_clause.c
+++ b/src/backend/parser/cypher_clause.c
@@ -207,6 +207,8 @@ static List *makeTargetListFromRTE(ParseState *pstate,
RangeTblEntry *rte);
static Query *transform_cypher_sub_pattern(cypher_parsestate *cpstate,
cypher_clause *clause);
+static Query *transform_cypher_sub_query(cypher_parsestate *cpstate,
+ cypher_clause *clause);
// set and remove clause
static Query *transform_cypher_set(cypher_parsestate *cpstate,
cypher_clause *clause);
@@ -223,6 +225,8 @@ static List
*transform_cypher_delete_item_list(cypher_parsestate *cpstate,
List *delete_item_list,
Query *query);
//set operators
+static cypher_clause *make_cypher_clause(List *stmt);
+
static Query *transform_cypher_union(cypher_parsestate *cpstate,
cypher_clause *clause);
@@ -384,6 +388,10 @@ Query *transform_cypher_clause(cypher_parsestate *cpstate,
{
result = transform_cypher_sub_pattern(cpstate, clause);
}
+ else if (is_ag_node(self, cypher_sub_query))
+ {
+ result = transform_cypher_sub_query(cpstate, clause);
+ }
else if (is_ag_node(self, cypher_unwind))
{
result = transform_cypher_unwind(cpstate, clause);
@@ -2718,6 +2726,54 @@ static Query
*transform_cypher_sub_pattern(cypher_parsestate *cpstate,
return qry;
}
+static Query *transform_cypher_sub_query(cypher_parsestate *cpstate,
+ cypher_clause *clause)
+{
+ cypher_clause *c;
+ Query *qry;
+ ParseState *pstate =(ParseState *)cpstate;
+ cypher_sub_query *sub_query = (cypher_sub_query*)clause->self;
+ RangeTblEntry *rte;
+ cypher_parsestate *child_parse_state = make_cypher_parsestate(cpstate);
+ ParseState *p_child_parse_state = (ParseState *) child_parse_state;
+ p_child_parse_state->p_expr_kind = pstate->p_expr_kind;
+
+ c = make_cypher_clause((List *)sub_query->query);
+
+ qry = makeNode(Query);
+ qry->commandType = CMD_SELECT;
+
+ child_parse_state->subquery_where_flag = true;
+
+ rte = transform_cypher_clause_as_subquery(child_parse_state,
+ transform_cypher_clause,
+ c,
+ NULL, true);
+
+ qry->targetList = makeTargetListFromRTE(p_child_parse_state, rte);
+
+ markTargetListOrigins(p_child_parse_state, qry->targetList);
+
+ qry->rtable = p_child_parse_state->p_rtable;
+ qry->jointree = makeFromExpr(p_child_parse_state->p_joinlist, NULL);
+
+ /* the state will be destroyed so copy the data we need */
+ qry->hasSubLinks = p_child_parse_state->p_hasSubLinks;
+ qry->hasTargetSRFs = p_child_parse_state->p_hasTargetSRFs;
+ qry->hasAggs = p_child_parse_state->p_hasAggs;
+
+ if (qry->hasAggs)
+ {
+ parse_check_aggregates(p_child_parse_state, qry);
+ }
+
+ assign_query_collations(p_child_parse_state, qry);
+
+ free_cypher_parsestate(child_parse_state);
+
+ return qry;
+}
+
/*
* Code borrowed and inspired by PG's transformFromClauseItem. This function
* will transform the VLE function, depending on type. Currently, only
@@ -4826,8 +4882,20 @@ static Expr *transform_cypher_edge(cypher_parsestate
*cpstate,
/*
* If we are in a WHERE clause transform, we don't want to create new
* variables, we want to use the existing ones. So, error if otherwise.
+ * If we are in a subquery transform, we are allowed to create new
variables
+ * in the match, and all variables outside are visible to
+ * the subquery. Since there is no existing SQL logic that allows
+ * subqueries to alter variables of outer queries, we bypass this
+ * logic we would normally use to process WHERE clauses.
+ *
+ * Currently, the EXISTS subquery logic is naive. It returns a boolean
+ * result on the outer queries, but does not restrict the results set.
+ *
+ * TODO: Implement logic to alter outer scope results.
+ *
*/
- if (pstate->p_expr_kind == EXPR_KIND_WHERE)
+ if (pstate->p_expr_kind == EXPR_KIND_WHERE &&
+ cpstate->subquery_where_flag == false)
{
cypher_parsestate *parent_cpstate =
(cypher_parsestate *)pstate->parentParseState->parentParseState;
@@ -5084,8 +5152,20 @@ static Expr *transform_cypher_node(cypher_parsestate
*cpstate,
/*
* If we are in a WHERE clause transform, we don't want to create new
* variables, we want to use the existing ones. So, error if otherwise.
+ * If we are in a subquery transform, we are allowed to create new
variables
+ * in the match, and all variables outside are visible to
+ * the subquery. Since there is no existing SQL logic that allows
+ * subqueries to alter variables of outer queries, we bypass this
+ * logic we would normally use to process WHERE clauses.
+ *
+ * Currently, the EXISTS subquery logic is naive. It returns a boolean
+ * result on the outer queries, but does not restrict the results set.
+ *
+ * TODO: Implement logic to alter outer scope results.
+ *
*/
- if (pstate->p_expr_kind == EXPR_KIND_WHERE)
+ if (pstate->p_expr_kind == EXPR_KIND_WHERE &&
+ cpstate->subquery_where_flag == false)
{
cypher_parsestate *parent_cpstate =
(cypher_parsestate *)pstate->parentParseState->parentParseState;
diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y
index a3d6824e..806456ca 100644
--- a/src/backend/parser/cypher_gram.y
+++ b/src/backend/parser/cypher_gram.y
@@ -98,6 +98,9 @@
reading_clause_list updating_clause_list_0 updating_clause_list_1
%type <node> reading_clause updating_clause
+%type <list> subquery_stmt subquery_stmt_with_return subquery_stmt_no_return
+ single_subquery single_subquery_no_return subquery_part_init
+
/* RETURN and WITH clause */
%type <node> return return_item sort_item skip_opt limit_opt with
%type <list> return_item_list order_by_opt sort_item_list
@@ -150,6 +153,8 @@
%type <list> expr_list expr_list_opt map_keyval_list_opt map_keyval_list
%type <node> property_value
+%type <node> expr_subquery
+
/* names */
%type <string> property_key_name var_name var_name_opt label_name
%type <string> symbolic_name schema_name
@@ -593,6 +598,90 @@ updating_clause:
| merge
;
+subquery_stmt:
+ subquery_stmt_with_return
+ {
+ $$ = $1;
+ }
+ | subquery_stmt_no_return
+ {
+ $$ = $1;
+ }
+ ;
+
+subquery_stmt_with_return:
+ single_subquery
+ {
+ $$ = $1;
+ }
+ | subquery_stmt_with_return UNION all_or_distinct subquery_stmt_with_return
+ {
+ $$ = list_make1(make_set_op(SETOP_UNION, $3, $1, $4));
+ }
+ ;
+
+subquery_stmt_no_return:
+ single_subquery_no_return
+ {
+ $$ = $1;
+ }
+ | subquery_stmt_no_return UNION all_or_distinct subquery_stmt_no_return
+ {
+ $$ = list_make1(make_set_op(SETOP_UNION, $3, $1, $4));
+ }
+ ;
+
+single_subquery:
+ subquery_part_init reading_clause_list return
+ {
+ $$ = list_concat($1, lappend($2, $3));
+ }
+ ;
+
+single_subquery_no_return:
+ subquery_part_init reading_clause_list
+ {
+ ColumnRef *cr;
+ ResTarget *rt;
+ cypher_return *n;
+
+ /*
+ * since subqueries allow return-less clauses, we add a
+ * return node manually to reflect that syntax
+ */
+ cr = makeNode(ColumnRef);
+ cr->fields = list_make1(makeNode(A_Star));
+ cr->location = @1;
+
+ rt = makeNode(ResTarget);
+ rt->name = NULL;
+ rt->indirection = NIL;
+ rt->val = (Node *)cr;
+ rt->location = @1;
+
+ n = make_ag_node(cypher_return);
+ n->distinct = false;
+ n->items = list_make1((Node *)rt);
+ n->order_by = NULL;
+ n->skip = NULL;
+ n->limit = NULL;
+
+ $$ = list_concat($1, lappend($2, n));
+
+ }
+ ;
+
+subquery_part_init:
+ /* empty */
+ {
+ $$ = NIL;
+ }
+ | subquery_part_init reading_clause_list with
+ {
+ $$ = lappend(list_concat($1, $2), $3);
+ }
+ ;
+
cypher_varlen_opt:
'*' cypher_range_opt
{
@@ -1791,6 +1880,53 @@ expr_func_subexpr:
}
;
+expr_subquery:
+ EXISTS '{' anonymous_path '}'
+ {
+ /*
+ * EXISTS subquery with an anonymous path is almost
+ * the same as a EXISTS sub pattern, so we reuse that
+ * logic here to simplify more complex subquery transformations.
+ * TODO: Add WHERE clause support for anonymous paths in functions.
+ */
+
+ cypher_sub_pattern *sub;
+ SubLink *n;
+
+ sub = make_ag_node(cypher_sub_pattern);
+ sub->kind = CSP_EXISTS;
+ sub->pattern = list_make1($3);
+
+ n = makeNode(SubLink);
+ n->subLinkType = EXISTS_SUBLINK;
+ n->subLinkId = 0;
+ n->testexpr = NULL;
+ n->operName = NIL;
+ n->subselect = (Node *) sub;
+ n->location = @1;
+ $$ = (Node *)node_to_agtype((Node *)n, "boolean", @1);
+ }
+ | EXISTS '{' subquery_stmt '}'
+ {
+ cypher_sub_query *sub;
+ SubLink *n;
+
+ sub = make_ag_node(cypher_sub_query);
+ sub->kind = CSP_EXISTS;
+ sub->query = $3;
+
+ n = makeNode(SubLink);
+
+ n->subLinkType = EXISTS_SUBLINK;
+ n->subLinkId = 0;
+ n->testexpr = NULL;
+ n->operName = NIL;
+ n->subselect = (Node *) sub;
+ n->location = @1;
+ $$ = (Node *)node_to_agtype((Node *)n, "boolean", @1);
+ }
+ ;
+
property_value:
expr_var '.' property_key_name
{
@@ -1824,6 +1960,7 @@ expr_atom:
| expr_case
| expr_var
| expr_func
+ | expr_subquery
;
expr_literal:
diff --git a/src/backend/parser/cypher_parse_node.c
b/src/backend/parser/cypher_parse_node.c
index 545790d6..331ca7b8 100644
--- a/src/backend/parser/cypher_parse_node.c
+++ b/src/backend/parser/cypher_parse_node.c
@@ -59,6 +59,7 @@ cypher_parsestate *make_cypher_parsestate(cypher_parsestate
*parent_cpstate)
cpstate->graph_name = parent_cpstate->graph_name;
cpstate->graph_oid = parent_cpstate->graph_oid;
cpstate->params = parent_cpstate->params;
+ cpstate->subquery_where_flag = parent_cpstate->subquery_where_flag;
}
return cpstate;
@@ -124,7 +125,7 @@ RangeTblEntry *find_rte(cypher_parsestate *cpstate, char
*varname)
}
/*
- * Generates a default alias name for when a query needs on and the parse
+ * Generates a default alias name for when a query needs one and the parse
* state does not provide one.
*/
char *get_next_default_alias(cypher_parsestate *cpstate)
diff --git a/src/include/nodes/ag_nodes.h b/src/include/nodes/ag_nodes.h
index f74cd135..fad0ae91 100644
--- a/src/include/nodes/ag_nodes.h
+++ b/src/include/nodes/ag_nodes.h
@@ -57,8 +57,9 @@ typedef enum ag_node_tag
cypher_typecast_t,
// integer constant
cypher_integer_const_t,
- // sub patterns
+ // sub patterns/queries
cypher_sub_pattern_t,
+ cypher_sub_query_t,
// procedure calls
cypher_call_t,
// create data structures
diff --git a/src/include/nodes/cypher_nodes.h b/src/include/nodes/cypher_nodes.h
index 3bf8e08d..79070fb0 100644
--- a/src/include/nodes/cypher_nodes.h
+++ b/src/include/nodes/cypher_nodes.h
@@ -22,7 +22,7 @@
#include "nodes/ag_nodes.h"
-/* cypher sub patterns */
+/* cypher sub patterns/queries */
typedef enum csp_kind
{
CSP_EXISTS,
@@ -37,6 +37,13 @@ typedef struct cypher_sub_pattern
List *pattern;
} cypher_sub_pattern;
+typedef struct cypher_sub_query
+{
+ ExtensibleNode extensible;
+ csp_kind kind;
+ List *query;
+} cypher_sub_query;
+
/*
* clauses
*/
diff --git a/src/include/nodes/cypher_outfuncs.h
b/src/include/nodes/cypher_outfuncs.h
index 6be3c01c..3963be0d 100644
--- a/src/include/nodes/cypher_outfuncs.h
+++ b/src/include/nodes/cypher_outfuncs.h
@@ -61,8 +61,9 @@ void out_cypher_typecast(StringInfo str, const ExtensibleNode
*node);
// integer constant
void out_cypher_integer_const(StringInfo str, const ExtensibleNode *node);
-// sub pattern
+// sub patterns/queries
void out_cypher_sub_pattern(StringInfo str, const ExtensibleNode *node);
+void out_cypher_sub_query(StringInfo str, const ExtensibleNode *node);
// procedure call
diff --git a/src/include/parser/cypher_parse_node.h
b/src/include/parser/cypher_parse_node.h
index e169802d..71759915 100644
--- a/src/include/parser/cypher_parse_node.h
+++ b/src/include/parser/cypher_parse_node.h
@@ -40,6 +40,7 @@ typedef struct cypher_parsestate
int default_alias_num;
List *entities;
List *property_constraint_quals;
+ bool subquery_where_flag; // flag for knowing we are in a subquery where
/*
* To flag when an aggregate has been found in an expression during an
* expression transform. This is used during the return_item list transform