Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# - cypher query with multiple return values

Given this query (from here)

  let pAfollowers =
        client.Cypher
            .Match("n<-[:follows]-e")
            .Where(fun n -> n.Twitter = "tA")
            .Return<Person>("e")
            .Results
            .Select(fun x -> x.Name)

I would like to tweak it and have it return multiple values packaged together. Not sure about how the type should look:

let pAfollowers =
        client.Cypher
            .Match("n<-[r:follows]-e")
            .Where(fun n -> n.Twitter = "tA")
            .Return<???>("n, r, e")

Secondly I was wondering if it is possible to have a return statement after a CreateUnique. I am trying to tweak this query:

let knows target (details : Knows) source =
        client.Cypher
            .Match("(s:Person)", "(t:Person)")
            .Where(fun s -> s.Twitter = source.Twitter)
            .AndWhere(fun t -> t.Twitter = target.Twitter)
            .CreateUnique("s-[:knows {knowsData}]->t")
            .WithParam("knowsData", details)
            .ExecuteWithoutResults()

to have it return s, t and the details.

like image 330
NoIdeaHowToFixThis Avatar asked Apr 01 '14 11:04

NoIdeaHowToFixThis


2 Answers

OK, good news / bad news - though in practice the good is tempered with bad :(

Good first:

You can return after a CreateUnique, something like:

.CreateUnique("s-[:Knows {knowsData}]-t")
.WithParam("knowsData", details)
.Returns<???>( "s,t" )
.Results;

Bad news:

The bad news is that you probably can't do it in F#. Neo4jClient requires you to use either object initializers, or anonymous types to cast the data, so you could try something like:

type FollowingResults = { Follower : Person; Followed : Person;}

let createExpression quotationExpression = LeafExpressionConverter.QuotationToLambdaExpression quotationExpression

let pAfollowers =
    client.Cypher
        .Match("n<-[:follows]-e")
        .Where(fun n -> n.Twitter = "tA")
        .Return(createExpression <@ Func<ICypherResultItem, ICypherResultItem, FollowingResults>(fun (e : Cypher.ICypherResultItem) (n : Cypher.ICypherResultItem) -> {Follower = e.As<Person>(); Followed = n.As<Person>()}) @>)
        .Results
        .Select(fun x -> x)

for follower in pAfollowers do
    printfn "%s followed %s" follower.Follower.Name follower.Followed.Name

For which the F# compiler will have no problems at all. However, Neo4jClient will throw an Argument exception with the following message:

The expression must be constructed as either an object initializer (for example: n => new MyResultType { Foo = n.Bar }), an anonymous type initializer (for example: n => new { Foo = n.Bar }), a method call (for example: n => n.Count()), or a member accessor (for example: n => n.As().Bar). You cannot supply blocks of code (for example: n => { var a = n + 1; return a; }) or use constructors with arguments (for example: n => new Foo(n)).

The problem being, F# doesn't have object initializers, nor anonymous types, you can wrangle with the F# stuff for ages and not get anywhere, as the C# doesn't recognize the F# initialization.

like image 81
Charlotte Skardon Avatar answered Sep 19 '22 11:09

Charlotte Skardon


I have somewhat good news to both. This code will compile just fine using tuples and can be used with a modified Neo4jClient that supports F# Tuples: https://github.com/n074v41l4bl34u/Neo4jClient Solution is based on: https://fsharppowerpack.codeplex.com/workitem/4572

  let knows target (details : Knows) source =
    client.Cypher
      .Match("(s:Person)", "(t:Person)")
      .Where(fun s -> s.Twitter = source.Twitter)
      .AndWhere(fun t -> t.Twitter = target.Twitter)
      .CreateUnique("s-[:knows {knowsData}]->t")
      .WithParam("knowsData", details)
      .Return(fun s t -> s.As<Person>(),t.As<Person>())

  let pAfollowers =
    client.Cypher
      .Match("n<-[:follows]-e")
      .Where(fun n -> n.Twitter = "tA")
      .Return(fun (e : Cypher.ICypherResultItem) n -> e.As<Person>().Name,n.As<Person>().Name)

The type annotation on '(e : Cypher.ICypherResultItem)' can be omited when using more than one argument in fun.

However, when using a single argument, this gets rid off the ugly createExpression <@ Func(...) @>) syntax. For details on why look on the bottom of this page: https://gist.github.com/cskardon/8300420

like image 38
TermoTux Avatar answered Sep 21 '22 11:09

TermoTux