Support nicer Cypher Start notation

Issue #74 resolved
Tatham Oddie
created an issue

Something like:

Start(new {
    Foo = someNodeReference,
    Bar = All.Nodes,
    Baz = NodeIndexLookup(...)
})

Comments (9)

  1. Tatham Oddie reporter

    The Start method has seen a significant overhaul that makes it much more flexible, whilst also much nicer to read.

    The new structure uses an anonymous type to capture the identity. For example:

    graphClient
        .Cypher
        .Start(new { foo = nodeRef });
    

    This means that multiple start bits are now much more intuitive than they used to be:

    graphClient
        .Cypher
        .Start(new {
            foo = nodeRef,
            bar = otherNodeRef
        });
    

    Next, we can combine multiple types of start bits more easily:

    graphClient
        .Cypher
        .Start(new {
            foo = nodeRef,
            bar = otherNodeRef,
            books = Node.ByIndexQuery("books", "author:Mike"),
            all = All.Nodes
        });
    

    Equivalent code in the old format is much less readable:

    graphClient
        .Cypher
        .Start(
            new CypherStartBit("foo", nodeRef),
            new CypherStartBit("bar", otherNodeRef),
            new CypherStartBitWithNodeIndexLookupWithSingleParameter("books", "books", "author:mike"),
            new RawCypherStartBit("all", "node(*)")
        );
    

    Relationship Indexes

    This update also exposes Relationship.ByIndexLookup and Relationship.ByIndexQuery, for which there were no previous equivalents.

    Custom Start Bits

    In the case where we still don't support a specific bit for some reason, you can now just supply a string:

    graphClient
        .Cypher
        .Start(new {
            foo = nodeRef,
            bar = "custom"
        });
    

    That results in START foo=node(123), bar=custom.

    Dynamic Identities

    If you don't know your identities at compile time, you can supply a dictionary instead of an anonymous object:

    graphClient
        .Cypher
        .Start(new Dictionary<string, object>
        {
            { "foo", nodeRef },
            { "bar", otherNodeRef }
        });
    

    All Together Now

    This means we can now support crazily complex start clauses, like so:

    graphClient
        .Cypher
        .Start(new {
            n1 = "custom",
            n2 = nodeRef,
            n3 = Node.ByIndexLookup("indexName", "property", "value"),
            n4 = Node.ByIndexQuery("indexName", "query"),
            r1 = relRef,
            moreRels = new[] { relRef, relRef2 },
            r2 = Relationship.ByIndexLookup("indexName", "property", "value"),
            r3 = Relationship.ByIndexQuery("indexName", "query"),
            all = All.Nodes
        });
    

    Produces:

    START
        n1=custom,
        n2=node({p0}),
        n3=node:indexName(property = {p1}),
        n4=node:indexName({p2}),
        r1=relationship({p3}),
        moreRels=relationship({p4}, {p5}),
        r2=relationship:indexName(property = {p6}),
        r3=relationship:indexName({p7}),
        all=node(*)
    

    Migration

    The basic Start("foo", nodeRef) and Start("foo", relRef) overloads have not been marked as obsolete due to the amount of unnecessary migration work such a change would create. They should be avoided for new code though, so you can avoid the magic strings and make it easier to add new start bits to your query in the future.

    Specific methods like StartWithNodeIndexLookup and Start(string identity, string startText) have been marked as obsolete. You should migrate off these immediately.

    CypherStartBit, CypherStartBitWithNodeIndexLookup, CypherStartBitWithNodeIndexLookupWithSingleParameter and RawCypherStartBit have been marked as obsolete. You should migrate off these immediately.

    ICypherStartBit has been marked as obsolete. It is unlikely that you would have touched this interface directly. If you have, then you probably need to migrate your code to use StartBit instead. See the implementation of Node.ByIndexLookup for an example of how.

  2. Tatham Oddie reporter

    Sergey,

    From F#, you should be able to use this overload instead:

    ICypherFluentQuery Start(IDictionary<string, object> startBits)
    

    Then, you can do the F# equivalent of:

    .Start(new Dictionary<string,object> {
        { "foo", nodeRef }
    })
    

    That doesn't use any anonymous types.

    If you look at the code, all we do for the object overload is convert it to a dictionary, then call the dictionary overload: https://bitbucket.org/Readify/neo4jclient/src/db5e57a78d3e5032ce5da142272fa3278eed744c/Neo4jClient/Cypher/StartBitFormatter.cs?at=default#cl-10

    There seems to be a few people using Neo4jClient with F#. Perhaps if you could share the appropriate F# code, I can include it in the doco?

    It might also be a good idea for us to create some syntax-based "unit" tests written in F#.

    HTH!

  3. Sergey Tihon

    "All Together" start clauses, will look like this in F#:

    graphClient
        .Cypher
        .Start(dict [
            ("n1", box "custom");
            ("n2", box nodeRef);
            ("n3", box Node.ByIndexLookup("indexName", "property", "value"));
            ("n4", box Node.ByIndexQuery("indexName", "query"));
            ("r1", box relRef);
            ("moreRels", box [|relRef; relRef2|]);
            ("r2", box Relationship.ByIndexLookup("indexName", "property", "value"));
            ("r3", box Relationship.ByIndexQuery("indexName", "query"));
            ("all", box All.Nodes)
        ])
    

    A bit more explanation here: http://sergeytihon.wordpress.com/2013/04/25/neo4jclient-new-cypher-start-notation-f-extra-top-level-operators/

  4. Log in to comment