Delete Relationship Subscriptions
Subscriptions to DELETE_RELATIONSHIP
events will listen for relationships to a node of the specified type that have been deleted.
This subscription operation is only available for types that define relationship fields. |
As relationship-specific information, the event will contain the relationship field name, as well as an object containing all relationship field names of the specified type. This object will be populated with properties according to the deleted relationship.
A new event will be triggered for each deleted relationship. |
This means that, if the type targeted for the subscriptions defines two or more relationships in the schema and one of each relationships are deleted following a mutation operation, the number of events triggered will be equivalent to the number of relationships deleted.
Each event will have the relationships object populated with the deleted relationship’s properties for one single relationship name only - all other relationship names will have a null value.
The event will also contain the properties of the nodes at both ends of the relationship, as well as the properties of the new relationship, if any.
The DELETE_RELATIONSHIP events represent relationships being deleted and contain information about the nodes at each end of the new relationship. However, the disconnected nodes may or may not have been deleted in the process. To subscribe to the node’s updates, you need to use the DELETE subscriptions.
|
DELETE_RELATIONSHIP
event
A subscription to a type can be made with the top-level subscription [type]RelationshipDeleted
. The subscription will contain the following fields:
-
event
: The event triggering this subscription, in this case it will always be"DELETE_RELATIONSHIP"
. -
timestamp
: The timestamp in which the mutation was made. Note that multiple events may come with the same timestamp if triggered by the same query. -
<typename>
: The properties of the node target to the subscription. Only top-level properties, without relationships, are available. Note these are the properties before the operation that triggered theDELETE_RELATIONSHIP
took place. -
relationshipFieldName
: The field name of the deleted relationship, as part of the node target to the subscription. -
deletedRelationship
: An object having as keys all field names of the node target to the subscription which represent its relationships. For any given event, all field names except the one corresponding torelationshipFieldName
will be null. The field name equal torelationshipFieldName
will contain the relationship properties if defined, and anode
key containing the properties of the node on the other side of the relationship. Only top-level properties, without relationships, are available. Note these are the properties before the operation that triggered theDELETE_RELATIONSHIP
took place.
Irrespective of the relationship direction in the database, the DELETE_RELATIONSHIP event is bound to the type targeted for the subscription. The consequence is that - given a relationship between types A and B that is not reciprocal (that is, in the GraphQL schema type A defines a relationship to B but B does not define a relationship to A) and a GraphQL operation that deletes the relationship between them - even though the two nodes will be disconnected in the database, the DELETE_RELATIONSHIP event will only be returned to the subscription to type A. Check out the Non-reciprocal Relationships section below for more details.
|
For example, considering the following type definitions:
type Movie {
title: String
genre: String
actors: [Actor] @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn")
reviewers: [Reviewer] @relationship(type: "REVIEWED", direction: IN, properties: "Reviewed")
}
type Actor {
name: String
}
interface ActedIn @relationshipProperties {
screenTime: Int!
}
type Reviewer {
name: String
reputation: Int
}
interface Reviewed @relationshipProperties {
score: Int!
}
An ongoing subscription to deleted relationships from the Movie
type, upon a mutation deleting then Actor
named Tom Hardy
and the Reviewer
named Jane
from a Movie
titled Inception
would receive the following events:
{
# 1 - relationship type `ACTED_IN`
event: "DELETE_RELATIONSHIP",
timestamp,
movie: {
title: "Inception",
genre: "Adventure"
},
relationshipFieldName: "actors", # notice the field name specified here is populated below in the `createdRelationship` object
deletedRelationship: {
actors: {
screenTime: 1000, # relationship properties for the relationship type `ACTED_IN` that was deleted
node: { # top-level properties of the node at the other end of the relationship, in this case `Actor` type, before the delete occured
name: "Tom Hardy"
}
},
reviewers: null # relationship declared by this field name is not covered by this event, check out the following...
}
}
{
# 2 - relationship type `REVIEWED`
event: "DELETE_RELATIONSHIP",
timestamp,
movie: {
title: "Inception",
genre: "Adventure"
},
relationshipFieldName: "reviewers", # this event covers the relationship declared by this field name
deletedRelationship: {
actors: null, # relationship declared by this field name is not covered by this event
reviewers: { # field name equal to `relationshipFieldName`
score: 8,
node: {
name: "Jane",
reputation: 9
}
}
}
}
Examples
Delete Relationships with Standard Types
For example, considering the following type definitions:
type Movie {
title: String
genre: String
actors: [Actor] @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn")
}
type Actor {
name: String
}
interface ActedIn @relationshipProperties {
screenTime: Int!
}
A subscription to any Movie
deleted relationships would look like:
subscription {
movieRelationshipDeleted {
event
timestamp
movie {
title
genre
}
relationshipFieldName
deletedRelationship {
actors {
screenTime
node {
name
}
}
}
}
}
Delete Relationship with Abstract Types
When using Abstract Types with relationships, you will need to specify one or more of the corresponding Concrete Types when performing the subscription operation.
These types are generated by the library and conform to the format [type]EventPayload
, where [type]
is a Concrete Type.
Union Example
Considering the following type definitions:
type Movie {
title: String
genre: String
directors: [Director!]! @relationship(type: "DIRECTED", properties: "Directed", direction: IN)
}
union Director = Person | Actor
type Actor {
name: String
}
type Person {
name: String
reputation: Int
}
interface Directed @relationshipProperties {
year: Int!
}
A subscription to Movie
deleted relationships would look like:
subscription {
movieRelationshipDeleted {
event
timestamp
movie {
title
genre
}
relationshipFieldName
deletedRelationship {
directors {
year
node {
... on PersonEventPayload { # generated type
name
reputation
}
... on ActorEventPayload { # generated type
name
}
}
}
}
}
}
Interface Example
Considering the following type definitions:
type Movie {
title: String
genre: String
reviewers: [Reviewer!]! @relationship(type: "REVIEWED", properties: "Review", direction: IN)
}
interface Reviewer {
reputation: Int!
}
type Magazine implements Reviewer {
title: String
reputation: Int!
}
type Influencer implements Reviewer {
name: String
reputation: Int!
}
interface Review {
score: Int!
}
A subscription to Movie
deleted relationships would look like:
subscription {
movieRelationshipDeleted {
event
timestamp
movie {
title
genre
}
relationshipFieldName
deletedRelationship {
reviewers {
score
node {
reputation
... on MagazineEventPayload { # generated type
title
reputation
}
... on InfluencerEventPayload { # generated type
name
reputation
}
}
}
}
}
}
Non-reciprocal relationships
Considering the following type definitions:
type Movie {
title: String
genre: String
actors: [Actor] @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn")
directors: [Director!]! @relationship(type: "DIRECTED", properties: "Directed", direction: IN)
}
type Actor {
name: String
movies: [Movie!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: OUT)
}
type Person {
name: String
reputation: Int
}
union Director = Person | Actor
interface ActedIn @relationshipProperties {
screenTime: Int!
}
interface Directed @relationshipProperties {
year: Int!
}
The type definitions contain 2 relationships: types ACTED_IN
and DIRECTED
.
It can be observed that the ACTED_IN
relationship has a corresponding field defined in both the Movie
and Actor
types. As such, we can say that ACTED_IN
is a reciprocal relationship.
DIRECTED
on the other hand is only defined in the Movie
type. The Director
type does not define a matching field. As such, we can say DIRECTED
is not a reciprocal relationship.
Let us now take a look at how we can subscribe to deleted relationships for the 3 types defined above:
Movie
subscription {
movieRelationshipDeleted {
event
timestamp
movie {
title
genre
}
relationshipFieldName
deletedRelationship {
actors { # corresponds to the `ACTED_IN` relationship type
screenTime
node {
name
}
}
directors { # corresponds to the `DIRECTED` relationship type
year
node {
... on PersonEventPayload {
name
reputation
}
... on ActorEventPayload {
name
}
}
}
}
}
}
Person
As the Person
type does not define any relationships, it is not possible to subscribe to DELETE_RELATIONSHIP
events for this type.
Actor
subscription {
actorRelationshipDeleted {
event
timestamp
actor {
name
}
relationshipFieldName
deletedRelationship {
movies { # corresponds to the `ACTED_IN` relationship type
screenTime
node {
title
genre
}
}
# no other field corresponding to the `DIRECTED` relationship type
}
}
}
The presence of the movie
field inside of deletedRelationship
for the actorRelationshipDeleted
subscription reflects the fact that the ACTED_IN
typed relationship is reciprocal.
Therefore, when a relationship of this type is deleted, such as by running the following mutations:
mutation {
createMovies(
input: [
{
actors: {
create: [
{
node: {
name: "Keanu Reeves"
},
edge: {
screenTime: 420
}
}
]
},
title: "John Wick",
genre: "Action"
}
]
) {
movies {
title
genre
}
}
}
mutation {
deleteMovies(
where: {
title: "John Wick"
}
) {
nodesDeleted
}
}
Two events will be published (given that we subscribed to DELETE_RELATIONSHIP
events on both types):
{
# from `movieRelationshipDeleted`
event: "DELETE_RELATIONSHIP"
timestamp
movie {
title: "John Wick",
genre: "Action"
}
relationshipFieldName: "actors",
deletedRelationship {
actors: {
screenTime: 420,
node: {
name: "Keanu Reeves"
}
},
directors: null
}
},
{
# from `actorRelationshipDeleted`
event: "DELETE_RELATIONSHIP"
timestamp
actor {
name: "Keanu Reeves"
}
relationshipFieldName: "movies",
deletedRelationship {
movies: {
screenTime: 420,
node: {
title: "John Wick",
genre: "Action"
}
}
}
}
Since the DIRECTED
relationship between types Movie
and Director
is not reciprocal, executing the following mutations:
mutation {
createMovies(
input: [
{
directors: {
Actor: { # relationship 1
create: [
{
node: {
name: "Woody Allen"
},
edge: {
year: 1989
}
}
]
},
Person: { # relationship 2
create: [
{
node: {
name: "Francis Ford Coppola",
reputation: 100
},
edge: {
year: 1989
}
}
]
}
},
title: "New York Stories",
genre: "Comedy"
}
]
) {
movies {
title
genre
}
}
}
mutation {
deleteMovies(
where: {
title: "New York Stories"
}
) {
nodesDeleted
}
}
Two events will be published (given that we subscribed to DELETE_RELATIONSHIP
events on the Movie
type):
{
# relationship 1 - from `movieRelationshipDeleted`
event: "DELETE_RELATIONSHIP"
timestamp
movie {
title: "New York Stories",
genre: "Comedy"
}
relationshipFieldName: "directors",
deletedRelationship {
actors: null,
directors: {
year: 1989,
node: {
name: "Woody Allen"
}
}
}
},
{
# relationship 2 - from `movieRelationshipDeleted`
event: "DELETE_RELATIONSHIP"
timestamp
movie {
title: "New York Stories",
genre: "Comedy"
}
relationshipFieldName: "directors",
deletedRelationship {
actors: null,
directors: {
year: 1989,
node: {
name: "Francis Ford Coppola",
reputation: 100
}
}
}
}
Special Considerations
Types using the same Neo4j label
One case that deserves special consideration is overriding the label in Neo4j for a specific GraphQL type.
This can be achieved using the @node
directive, by specifying the label
argument.
While this section serves an informative purpose, it should be mentioned that, in the majority of cases, this is not the recommended approach of designing your API. |
Consider the following type definitions:
type Actor @node(label: "Person") {
name: String
movies: [Movie!]! @relationship(type: "PART_OF", direction: OUT)
}
typePerson {
name: String
movies: [Movie!]! @relationship(type: "PART_OF", direction: OUT)
}
type Movie {
title: String
genre: String
people: [Person!]! @relationship(type: "PART_OF", direction: IN)
actors: [Actor!]! @relationship(type: "PART_OF", direction: IN)
}
Although we have 3 GraphQL types, in Neo4j there will only ever be 2 types of nodes: labeled Movie
or labeled Person
.
At the database level there is no distinction between Actor
and Person
. Therefore, when deleting a relationship of type PART_OF
, there will be one event for each of the 2 types.
Considering the following subscriptions:
subscription {
movieRelationshipDeleted {
event
timestamp
movie {
title
genre
}
relationshipFieldName
deletedRelationship {
people { # corresponds to the `PART_OF` relationship type
node {
name
}
}
actors { # corresponds to the `PART_OF` relationship type
node {
name
}
}
}
}
}
subscription {
actorRelationshipDeleted {
event
timestamp
actor {
name
}
relationshipFieldName
deletedRelationship {
movies { # corresponds to the `PART_OF` relationship type
node {
title
genre
}
}
}
}
}
Running the following mutations:
mutation {
createMovies(
input: [
{
people: { # relationship 1
create: [
{
node: {
name: "John Logan"
}
}
]
},
actors: { # relationship 2
create: [
{
node: {
name: "Johnny Depp"
}
}
]
},
title: "Sweeney Todd",
genre: "Horror"
}
]
) {
movies {
title
genre
}
}
}
mutation {
deleteMovies(
where: {
title: "Sweeney Todd"
}
) {
nodesDeleted
}
}
Result in the following events:
{
# relationship 1 `people` - for GraphQL types `Movie`, `Person`
event: "DELETE_RELATIONSHIP"
timestamp
movie {
title: "Sweeney Todd",
genre: "Horror"
}
relationshipFieldName: "people",
deletedRelationship {
people: {
node: {
name: "John Logan"
}
},
actors: null
}
},
{
# relationship 1 `people` - for GraphQL types `Movie`, `Actor`
event: "DELETE_RELATIONSHIP"
timestamp
movie {
title: "Sweeney Todd",
genre: "Horror"
}
relationshipFieldName: "actors",
deletedRelationship {
people: null,
actors: {
node: {
name: "John Logan"
}
}
}
},
{
# relationship 1 `movies` - for GraphQL types `Actor`, `Movie`
event: "DELETE_RELATIONSHIP"
timestamp
actor {
name: "John Logan"
}
relationshipFieldName: "movies",
deletedRelationship {
movies: {
node: {
title: "Sweeney Todd",
genre: "Horror"
}
}
}
},
{
# relationship 2 `actors` - for GraphQL types `Movie`,`Person`
event: "DELETE_RELATIONSHIP"
timestamp
movie {
title: "Sweeney Todd",
genre: "Horror"
}
relationshipFieldName: "people",
deletedRelationship {
people: {
node: {
name: "Johnny Depp"
}
},
actors: null
}
},
{
# relationship 2 `actors` - for GraphQL types `Movie`, `Actor`
event: "DELETE_RELATIONSHIP"
timestamp
movie {
title: "Sweeney Todd",
genre: "Horror"
}
relationshipFieldName: "actors",
deletedRelationship {
people: null,
actors: {
node: {
name: "Johnny Depp"
}
}
}
},
{
# relationship 2 `movies` - for GraphQL types `Actor`, `Movie`
event: "DELETE_RELATIONSHIP"
timestamp
actor {
name: "Johnny Depp"
}
relationshipFieldName: "movies",
deletedRelationship {
movies: {
node: {
title: "Sweeney Todd",
genre: "Horror"
}
}
}
},
Had we subscribed to Person
as well, we would have received two more events:
{
# relationship 1 `movies` - for GraphQL types `Person`, `Movie`
event: "DELETE_RELATIONSHIP"
timestamp
actor {
name: "John Logan"
}
relationshipFieldName: "movies",
deletedRelationship {
movies: {
node: {
title: "Sweeney Todd",
genre: "Horror"
}
}
}
},
{
# relationship 2 `movies` - for GraphQL types `Person`, `Movie`
event: "DELETE_RELATIONSHIP"
timestamp
actor {
name: "Johnny Depp"
}
relationshipFieldName: "movies",
deletedRelationship {
movies: {
node: {
title: "Sweeney Todd",
genre: "Horror"
}
}
}
},
Was this page helpful?