Avoiding string literals in Cypher queries

98 views
Skip to first unread message

Ant P

unread,
Dec 15, 2014, 9:31:19 AM12/15/14
to neo4j...@googlegroups.com
I'm looking to refactor out some of the repeated literals and common clauses in my Cypher queries (particularly for node/relationship labels). I've looked at using extensions for ICypherFluentQuery to strongly-type re-usable clauses (label-specific match clauses etc.) and have also looked at wrapping Neo4jClient in a domain-specific query builder w/ fluent API to do things like this:

query.MatchUser("u").MatchPost("p").WrittenBy("p", "u")...

Both show some promise but seem to get very clunky and convoluted once they need to deal with things like multiple where clauses, where clauses referencing relationship labels, parameters and - in particular for the wrapper option - return type mapping (very difficult to pass a lambda to map return types without directly exposing ICypherFluentQuery via the builder's API, which gets messy).

I was wondering if anyone could provide any guidance/examples on how they manage their queries at the application level as I am concerned with the growing number of string literals and duplicate clauses across my queries. I'm also a little concerned that this level of repetition will make adding in cross-cutting concerns like access control very difficult later on (as it will not be possible to use a single source of centralized logic for this with such disparate queries).

Does anyone have any thoughts?

Ant P

unread,
Dec 15, 2014, 9:55:52 AM12/15/14
to neo4j...@googlegroups.com
Another quick note - I've also considered keeping things a bit simpler and just using constants to replace literal labels but this leads to some very unwieldy format strings in queries.

Tatham Oddie

unread,
Dec 18, 2014, 8:33:01 PM12/18/14
to neo4j...@googlegroups.com

I use partially built queries as building blocks:

Ā 

ICypherFluentQuery FindUser(string email)

{

Ā Ā  return graphClient

Ā Ā Ā Ā Ā  .Match(ā€œ(User:user)ā€)

Ā Ā Ā Ā Ā  .Where((User user) => user.EmailAddress == email);

}

Ā 

Later on:

Ā 

Void DeleteUser(string email)

{

Ā Ā Ā  FindUser(email)

Ā Ā Ā Ā Ā Ā Ā  .Delete(ā€œuserā€)

Ā Ā Ā Ā Ā Ā Ā  .ExecuteWithoutResults();

}

Ā 

You can also augment existing queries:

Ā 

ICypherFluentQuery NotSoftDeleted(this ICypherFluentQuery query, string identity)

{

Ā Ā  return query. Some clause here;

}

Ā 

Use it as:

Ā 

FindUser(email).NotSoftDeleted(ā€œuserā€). …

Ā 

There’s an implicit contract here around passing identities around, but I haven’t found that to be unwieldy.

Ā 

An F# type provider would be probably the nicest solution in the long run…

Ā 

--

Tatham

--
You received this message because you are subscribed to the Google Groups "Neo4jClient" group.
To unsubscribe from this group and stop receiving emails from it, send an email to neo4jclient...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Ant P

unread,
Feb 26, 2015, 10:46:10 AM2/26/15
to neo4j...@googlegroups.com
Just an update on this.

I've gone down the route of creating an fluent/extension-method-based set of queries where you can do:

_client.Cypher.MatchUser("a").MatchPost("b").Where(o => o.WrittenBy("b", "a"))

"Match" and "Where" both have extension methods that take a "Func<GraphClause, GraphClause>" where GraphClause is a custom class that can fluently generate where clauses or property matching expressions.

This is quite nice to use but you run into problems when you want to match paths because the "clause" methods ('WrittenBy' in this case) take identities and are label-agnostic, you can't produce Cypher like this:

MATCH (t:Tag)<-[:TAGGED_WITH]-(p:Post)-[:WRITTEN_BY]->(u:User) WHERE u.Name = 'Bob' RETURN t

It's not possible to produce the above path without having a dedicated "clause" method for it, which results in having loads of duplication of the label strings all over the place. Instead you have to do something like:

MATCH (t:Tag) MATCH (p:Post) MATCH (u:User) WHERE (p)-[:WRITTEN_BY]->(u) AND (p)-[:TAGGED_WITH]->(t) AND u.Name = 'Bob' RETURN t

Where this would be implemented as:

_client.Cypher.MatchUser("u").MatchPost("p").MatchTag("t").Where(o => o.WrittenBy("p", "u"))
Ā  Ā  Ā  Ā  Ā  Ā  Ā  .AndWhere(o => o.TaggedWith("p", "t")).AndWhere(o => o.HasName("u", "Bob").Return("t")

On my current graph with ~1,000 nodes, this query takes around 4 times as long as the "single match" approach above. I'm not sure how bad this will get as it scales.

Any thoughts? Reverting to just dumping the whole path as a string into 'Match(string)' seems to negate the usefulness of the above.
Reply all
Reply to author
Forward
0 new messages