MATCH clauses coming after OPTIONAL MATCH clauses
For this compiler, it's possible to filter on optional vertices and edges. For instance, the GraphQL query in test_input_data.py is as follows:
{
Animal {
name @output(out_name: "animal_name")
in_Animal_ParentOf @optional {
name @tag(tag_name: "parent_name")
}
out_Animal_ParentOf {
alias @filter(op_name: "contains", value: ["%parent_name"])
}
}
}
in which the filter applies to a tag that may not exist in all result sets.
The compiler currently outputs incorrect Cypher for the query above.
CYPHER 9
MATCH (Animal___1:Animal)
OPTIONAL MATCH (Animal___1)<-[:Animal_ParentOf]-(Animal__in_Animal_ParentOf___1:Animal)
MATCH (Animal___1)-[:Animal_ParentOf]->(Animal__out_Animal_ParentOf___1:Animal)
WHERE ((Animal__in_Animal_ParentOf___1 IS null) OR
(Animal__in_Animal_ParentOf___1.name IN Animal__out_Animal_ParentOf___1.alias))
RETURN
Animal___1.name AS `animal_name`
It raises a syntax error because a MATCH clause cannot follow OPTIONAL MATCH clause ("Neo.ClientError.Statement.SyntaxError: MATCH cannot follow OPTIONAL MATCH (perhaps use a WITH clause between them)" is the precise message, although I didn't find this explicitly mentioned in the documentation).
A first attempt at solving this would be to simply re-order the clauses so all the MATCH clauses are at the top. However, this fails because the compiler and Cypher treat filtering on optional fields differently. For the compiler, when filtering at or within an optional vertex or edge, if a given result set is able to produce a value for the optional vertex field, the filter may cause the result set to be discarded if it does not match. However, this is not the case in Cypher, where it simply pretends the edge doesn't exist.
Consider the following illustrative sequence of queries:
create (a:Person {name:"a"})-[r1:FRIENDS]->(b:Person {name: "b"})-[r2:FRIENDS]->(c:Person {name:"c"})
match(c:Person {name:"c"})
create (d:Person {name:"d"})-[r:FRIENDS]->(c)```
match (b:Person)-[]->(c:Person {name:"c"})
optional match (a:Person)-[r]->(b:Person)
where a is null or type(r)="NOTFRIENDS"
return a, b, c, r
The last query here gives two rows:
| "a" | "b" | "c" | "r" |
|---|---|---|---|
| null | {"name":"d"} | {"name":"c"} | null |
| null | {"name":"b"} | {"name":"c"} | null |
Cypher ignores the edge from a to b that is of type FRIENDS, which is not the intended behavior for the compiler. The correct output should be
| "a" | "b" | "c" | "r" |
|---|---|---|---|
| null | {"name":"d"} | {"name":"c"} | null |
Update: when implementing fold, which made significant use of OPTIONAL MATCH clauses, it seems like having a WITH clause after each optional traversal is the way to go here.
I.e. the above query can be turned into
CYPHER 9
MATCH (Animal___1:Animal)
OPTIONAL MATCH (Animal___1)<-[:Animal_ParentOf]-(Animal__in_Animal_ParentOf___1:Animal)
WITH
Animal___1 AS Animal___1,
Animal__in_Animal_ParentOf___1 AS Animal__in_Animal_ParentOf___1
MATCH (Animal___1)-[:Animal_ParentOf]->(Animal__out_Animal_ParentOf___1:Animal)
WHERE ((Animal__in_Animal_ParentOf___1 IS null) OR
(Animal__in_Animal_ParentOf___1.name IN Animal__out_Animal_ParentOf___1.alias))
RETURN
Animal___1.name AS `animal_name`