I have a large application using a Neo4j database. There are dozens of methods that hold Cypher queries. In order to remove the duplicate code that would exist if each method stored its own complete query, I have created private methods that build small commonly used chunks of a query. For example, a method like:
MatchNodesWithLabel(string label)
would return a partial query like:
MATCH (node:label)
These method calls are then followed by methods that add WHERE or RETURN statements to build a full query. This is a small example, there are methods that match and filter whole sets of nodes and relations.
The problem arises when multiple WHERE statements need to be chained together. Cypher doesn't allow a WHERE statement to follow a WHERE statement:
// Invalid
MATCH (node)
WHERE node:label
WHERE node.property = value
...
so any method past the first WHERE must insert an AND statement instead:
// Valid
MATCH (node)
WHERE node:label
AND node.property = value
AND ...
This creates a method ordering problem, where certain methods can not be used after others. A method that inserts a WHERE statement (a Where() method) must precede an And() method. Where() methods then can't be used after an And() method, and And() methods can't be used before Where() methods.
Here are some possible (but ultimately broken) solutions I've come up with to solve this ordering problem:
- Duplicate every
Where()method into an equivalentAndWhere()method that does the same filtering, but usesANDinstead ofWHERE. Then in the calling code, use oneWhere()method first, andAndWhere()methods thereafter.- This is not an acceptable solution because of code duplication, and because it complicates the calling code. It just moves the method ordering problem to the calling code, instead of solving the problem. I want to be able to add a
Where()call whenever I want, without thinking about how my methods are ordered
- This is not an acceptable solution because of code duplication, and because it complicates the calling code. It just moves the method ordering problem to the calling code, instead of solving the problem. I want to be able to add a
- Add a
WHERE truestatement before all the other calls, then make everyWhere()method insert anANDstatement.- This only works if the methods in the calling code are specifically ordered. All
MATCHmethods have to precede theWHERE true, otherwise you could end up with a Cypher query like:
MATCH (node) WHERE true AND node:type MATCH (somethingElse) AND somethingElse.property = value
which is invalid sinceANDfollows aMATCHwithout aWHERE. So once again the responsibility of method ordering has moved to the calling code. With this solution, you have to manually add aWHERE trueevery time you start a new series of filtering calls.
- This only works if the methods in the calling code are specifically ordered. All
- Parse the existing query to determine whether a
WHEREor anANDshould be used at the start of everyWhere()method.- This seems ok at first, all you have to do is a check like
existingQuery.Contains("WHERE")and if its true, insertANDinstead. However this has the same problem as #2. If a newMATCHstatement is inserted, the check will still return true, but the query won't be in a state where anANDis a valid statement. So you'd have to parse further back in the query, checking forMATCHandWHEREstatements, and you'd have to keep track of the order they occurred in, etc... This is far too complicated.
- This seems ok at first, all you have to do is a check like
Is some Neo4j syntax that allows a WHERE to be inserted anywhere, or a query building solution that I haven't some across?