graphql icon indicating copy to clipboard operation
graphql copied to clipboard

Custom Connections

Open reckter opened this issue 2 years ago • 4 comments

Problem

Setup


type  Node {
    children: [Node!]! @relationship(type: "CHILDREN", type: OUT)
    user: [User!]! @relationship(type: "MEMBER_OF", type: IN)
    
    // This is the connection I want
    allUserOfChildren: AllUserOfChildrenConnection
}

type User {
   node: Node! @relationshp(type: "MEMBER_OF", type: OUT)
}

We have a Tree structure Node, that can have many children, in many nesting levels (nodes can be in more than one place in the Tree). Each Node has some Users assigned to it.

Now I want to get all Users, that are in all children of one Node. Ideally (because we use Relay) capturing all these Users In one Connection, so I can use the same Relay mechanisms, as with all the other Connections.

Describe the solution you'd like

I would somehow mark a connection as "custom" and supply some way of getting the Items I want, so this library can generate the correct connection out of it.

One Idea:

type Node {
    allUserOfChildren: [Users!]! @relationship(query: """
    MATCH (this) -[:CHILDREN*]->(node:Node)<-[:MEMBER_OF]-(user:User)
    RETURN user
    """)
}

I am not to familiar with the internal parts of this library though, so take this as an early stage idea. It could very well be a different directive, and require the Cypher query to meet certain requirements.

Cheers

reckter avatar Jan 26 '22 14:01 reckter

@reckter This can actually be achieved through the current implementation if connection fields were to be decoupled from @relationship fields. There really is no reason why this coupling should exist as connection fields are simply Relay style connections built from related nodes.

The @cypher directive can already return a list of nodes related in an arbitrary (or custom) way when given a statement. If one were to create a connection field from this @cypher field similar to how a connection field is created for each @relationship field, it should "just work". A simple subquery generated from the statement would suffice.

Here is a rough demonstration of this: https://github.com/neo4j/graphql/compare/dev...dmoree:feature/cypher-connection.

  • Unions and Interfaces aren't accounted for (more subqueries)
  • Nested connection have an apoc.cypher.runFirstColumn call that maybe needs to be reworked (although it should probably all be subqueries)
  • Having something like a $$source node and a $$target node in the statement may allow for more reliable translation for the additional labels, unions, interfaces, etc.

Perhaps @darrellwarde or @danstarns could comment on how they see these types of connections fitting into the library.

I added some tests based on the below graph: https://github.com/neo4j/graphql/commit/8b5e18834b01940b0f7a59503a90c6192e9b7983. Let me know if I misinterpreted something.

Screen Shot 2022-02-01 at 12 37 26 AM

dmoree avatar Feb 01 '22 09:02 dmoree

@dmoree Big 👍 If I understood it correctly, it generated a allUsersOfChildrenConnection, because User has a relationship to Object and allUsersOfChildren returns [Users!]!? So I wouldn't even have to tell the library that I want a connection, it would always do it based on those conditions in the schema? That sounds awesome, but I could also imagine people getting confused why these connections are being generated. It might warrant and "enable" flag on the @cypher directive, or a different directive all together, but that's a judgment call for maintainers for sure.

reckter avatar Feb 02 '22 10:02 reckter

If I understood it correctly, it generated a allUsersOfChildrenConnection, because User has a relationship to Object and allUsersOfChildren returns [Users!]!? So I wouldn't even have to tell the library that I want a connection, it would always do it based on those conditions in the schema?

That's right. The above will generate a {field}Connection anytime the field returns an array of nodes, unions, or interfaces. So you'll have both an allUsersOfChildren field and an allUsersOfChildrenConnection field.

That sounds awesome, but I could also imagine people getting confused why these connections are being generated. It might warrant and "enable" flag on the @cypher directive, or a different directive all together, but that's a judgment call for maintainers for sure.

I agree. This is also true of @relationship although there really is no way of working with the properties of the relationship without it. But if you didn't need to work with those and, as with this, wanted to skip the generation of a connection there probably should be a flag somewhere. Perhaps at the directive level? An enableConnection argument that is defaulted to true? I'll leave that for the maintainers as you pointed out.

dmoree avatar Feb 02 '22 16:02 dmoree

I agree. This is also true of @relationship although there really is no way of working with the properties of the relationship without it. But if you didn't need to work with those and, as with this, wanted to skip the generation of a connection there probably should be a flag somewhere. Perhaps at the directive level? An enableConnection argument that is defaulted to true? I'll leave that for the maintainers as you pointed out.

This should be the case for all generated fields/resolvers.

process0 avatar Feb 05 '22 20:02 process0