Open dmoree opened 3 years ago
Create and Connect
The above description outlines the case for special consideration for create and connect. To accommodate these operations a relationship type must be specified as a special property on an edge in order to disambiguate the relationship. Currently, edge
s only exist on connections when a properties
argument is specified on @relationship
. Once specified it takes the shape of the interface
that defines those properties. A further proposal would be to extend this edge
to include not only the properties of the relationship but also its type; a special field (edge._type
) would be reserved for this purpose. It is important to note that this is not a breaking change as edge
would only be required for @relationship
s that have multiple types associated with them, much the same way that the edge
field is required if there are required fields on the properties interface.
Example
mutation {
createMovies(
input: {
title: "The Matrix"
people: {
Director: {
create: [
{ node: { name: "Lana Wachowski" }, edge: { _type: DIRECTED } }
{ node: { name: "Lily Wachowski" }, edge: { _type: DIRECTED } }
]
}
Actor: {
create: [
{ node: { name: "Keanu Reeves" }, edge: { _type: ACTED_IN } }
{ node: { name: "Carrie-Ann Moss" }, edge: { _type: ACTED_IN } }
]
}
}
}
) {
movies {
title
peopleConnection{
totalCount
edges {
_type
node {
__typename
...on Director {
name
}
...on Actor {
name
}
}
}
}
}
}
}
{
"data": {
"createMovies": {
"movies": [
{
"title": "The Matrix",
"peopleConnection": {
"totalCount": 4,
"edges": [
{
"_type": "DIRECTED",
"node": {
"__typename": "Director",
"name": "Lana Wachowski"
}
},
{
"_type": "DIRECTED",
"node": {
"__typename": "Director",
"name": "Lily Wachowski"
}
},
{
"_type": "ACTED_IN",
"node": {
"__typename": "Actor",
"name": "Keanu Reeves"
}
},
{
"_type": "ACTED_IN",
"node": {
"__typename": "Actor",
"name": "Carrie-Ann Moss"
}
}
]
}
}
]
}
}
}
There a some things to note:
edge
field is required in the MoviePeopleDirectorCreateFieldInput
since @relationship
type is DIRECTED|ACTED_IN
. An error will be thrown if it is not provided.
edge
field has a required field _type
that is of type MoviePeopleRelationshipType
. This type is an enum holding the various types on the relationship; i.e.
enum MoviePeopleRelationshipType {
DIRECTED
ACTED_IN
}
edges
field of the response a new field _type
is exposed that holds a value of MoviePeopleRelationshipType
. This represents the type of edge that is being returned and is treated as a property of the edge.A Further Use Case
Multiple relationship types do not need to correspond to multiple nodes. They can and do relate a particular node to another. This can be demonstrated by extending the above schema to include Series
.
type Series {
id: ID! @id
title: String!
actors: [Actor!]! @relationship(type: "ACTED_IN|ACTING_IN", direction: IN)
}
Since series can be ongoing there will be some actors that are current and those that are past. This information is most effectively stored as the relationship type between Actor
and Series
. To get the current actors:
query {
series {
id
title
actorsConnection(where: { edge: { _type: ACTING_IN } }) {
totalCount
edges {
node {
name
}
}
}
}
}
Currently, edge
and edge_NOT
are added to ConnectionWhere
types if there are any relationship properties specified. Here these fields are also added in the case of multiple relationship types where edges can be filtered by type.
Lastly, it is important to reiterate that these are intended to be non-breaking changes.
Hi @dmoree, sorry for taking that long. After some discussion, we have a couple of questions regarding the design of this proposal that we would need to address before reviewing the PR.
First, and more importantly. In the Create and Connect example from above, what would stop users from creating invalid relationships?: e.g.
mutation {
createMovies(
input: {
title: "The Matrix"
people: {
Director: {
create: [
{ node: { name: "Lana Wachowski" }, edge: { _type: DIRECTED } }
{ node: { name: "Lily Wachowski" }, edge: { _type: ACTED_IN } }
]
}
Actor: {
create: [
{ node: { name: "Keanu Reeves" }, edge: { _type: DIRECTED } }
{ node: { name: "Carrie-Ann Moss" }, edge: { _type: ACTED_IN } }
]
}
}
}
) {
movies {
title
peopleConnection{
totalCount
edges {
_type
node {
__typename
...on Director {
name
}
...on Actor {
name
}
}
}
}
}
}
}
Union relationships for creation are not supported by Neo4j, and honestly, don't really make much sense as, in this edge case, you'll need to know who is an actor and who a director is anyway.
Union relationships working as a read-only could be a potential solution to this problem, as the use-case of querying for a union relationship makes sense.
Another, less important concern is the syntax. Using the string to define unions ACTOR|DIRECTOR
poses 2 problems:
ACTOR|DIRECTOR
is actually a valid label, any change in behaviour would be better defined in explicit GraphQL statements (e.g. different property, array or directive) rather than on the string, which ideally should be the escaped label:
CREATE (n:`Actor|Director`)
We have initial designs for a new directive which support more complex relationship paths in a read-only manner, but don't intend to add this functionality to the @relationship
directive.
Is your feature request related to a problem? Please describe. It can be helpful to have a field on a GraphQL type that corresponds to multiple relationship types in the underlying graph. As a familiar example, take for instance the
typeDefs
:This is a one-to-one representation of the nodes in the database where
(:Director)
nodes are related to a(:Movie)
node by a single relationship[:DIRECTED]
and(:Actor)
nodes are related to(:Movie)
nodes by a single relationship[:ACTED_IN]
.A typical question of
Movie
would be: who are the associated people? In order to pull these nodes from the database a cypher query of the formwill return what is being sought after i.e.
(:Director)
and(:Actor)
nodes. Fortunately, GraphQL has an abstract typeUnion
that can be used to describe the associated types. This would lead to extending thetypeDefs
with:The obvious type for the
@relationship
directive would be"DIRECTED|ACTED_IN"
as it is the only way not to bring the abstraction ofPerson
into the database (one wouldn't want to add superfluous relationships like[:SEARCH]
or[:PERSON]
in order to facilitate a query that is as simple as the one above). Therefore, a proposal would be to allow multiple types to the@relationship
directive so that a natural extension would be:If
[:DIRECTED|ACTED_IN]
is used strictly for traversal, thenwhere
,update
,disconnect
, anddelete
through thepeople
field should be preserved as these only affect the underlying nodes.connect
andcreate
would have to be disabled as the relationship would be ambiguous and could not be resolved, although this may have a solution as well if one were to move beyond using this only to traverse the graph.Describe the solution you'd like Currently,
@neo4j/graphql
will handle traversing the graph quite easily with multiple types as long as the parameter name is transformed to convert|
to_
, but will also generate types in the schema related tocreate
andconnect
that would be disallowed using the above reasoning.Describe alternatives you've considered Besides introducing additional relationships in the database, I don't believe there is an alternative solution.