Filtering
This page covers how to apply filters to subscriptions in the Neo4j GraphQL Library. Note, however, that:
-
Filtering can only be applied at the root of the subscription operation.
-
Aggregations are not supported on subscription types and there is currently no available method.
A subscription can be created to target the changes to a node (create
/update
/delete
).) or to a relationship (`create
/`delete
While the format slightly differs depending on whether the subscription targets a node or a relationship, providing a where
argument allows for filtering on the events that are returned to the subscription.
Operators
When creating a subscription, a number of operators are available for different types in the where
argument.
Equality operators
All types can be tested for either equality or non-equality.
For the Boolean
type, these are the only available comparison operators.
Numerical operators
The following comparison operators are available for numeric types (Int
, Float
, BigInt
):
-
_LT
-
_LTE
-
_GTE
-
_GT
Filtering on Temporal types and Spatial types is not currently supported. |
String comparison
The following case-sensitive comparison operators are only available for use on String
and ID
types:
-
_STARTS_WITH
-
_ENDS_WITH
-
_CONTAINS
Array comparison
The following operator is available on non-array fields, and accepts an array argument:
-
_IN
Conversely, the following operator is available on array fields, and accepts a single argument:
-
_INCLUDES
These operators are available for all types apart from Boolean
.
AND
/OR
operators
Complex combinations of operators are possible using the AND
/ OR
operators.
They accept as argument an array of items of the same format as the where
argument.
Check Combining operators for an example.
Subscribing to node events
The where
argument allows specifying filters on top-level properties of the targeted nodes.
Only events matching these properties and type are returned to the subscription.
As an example, consider the following type definitions:
type Movie {
title: String
genre: String
averageRating: Float
releasedIn: Int
}
Now, here is how filtering can be applied when creating a subscription:
CREATE
To filter subscriptions to create
operations for movies by their genre, here is how to do it:
subscription {
movieCreated(where: {genre: "Drama"}) {
createdMovie {
title
}
}
}
This way, only newly created movies with the genre "Drama"
trigger events to this subscription.
The |
UPDATE
Here is how to filter subscription to update
operations in movies with average rating bigger than 8:
subscription {
movieUpdate(where: {averageRating_GT: 8}) {
updatedMovie {
title
}
}
}
This way, you should only receive events triggered by movies with the average rating bigger than 8 when they are modified.
The |
This is how these events may look like:
mutation {
makeTheMatrix: createMovies(input: {title: "The Matrix", averageRating: 8.7}) {
title
averageRating
},
makeResurrections: createMovies(input: {title: "The Matrix Resurrections", averageRating: 5.7}) {
title
averageRating
},
}
mutation {
updateTheMatrix: updateMovie(
where: {title: "The Matrix"}
update: {averageRating: 7.9}
) {
title
},
updateResurrections: updateMovie(
where: {title: "The Matrix Resurrections"}
update: {averageRating: 8.9}
) {
title
}
}
Therefore, given the previously described subscription, these GraphQL operations should only triggered for "The Matrix"
movie.
DELETE
Here is how to filter subscription to delete
operations in movies by their genre, using the NOT
filter:
subscription {
movieDeleted(where: { NOT: { genre: "Comedy" } }) {
deletedMovie {
title
}
}
}
This way, only deleted movies of all genres except for "Comedy"
should trigger events to this subscription.
The |
Combining operators
All previously mentioned operators can be combined using the AND
, OR
, and NOT
operators.
They accept an array argument with items of the same format as the where
argument, which means they can also be nested to form complex combinations.
As an exampke, consider a user who likes comedy movies, but not romantic comedies from early 2000, and who has the Matrix Trilogy as their favorite titles. They could subscribe to any updates that cover this particular set of interests as follows:
subscription {
movieUpdated(where: {
OR: [
{ title_CONTAINS: "Matrix" },
{ genre: "comedy" },
{ AND: [
{ NOT: { genre: "romantic comedy" } },
{ releasedIn_GT: 2000 },
{ releasedIn_LTE: 2005 }
] },
]
}) {
updatedMovie {
title
}
}
}
Subscribing to relationship events
When subscribing to relationship events, the where
argument still allows specifying filters on the top-level properties of the targeted nodes.
It also supports specifying filters on the relationship properties (edge
) and on the top-level properties (node
) of the nodes at the other end of the relationship.
This is done by using the operators previously described, and the usage is very similar to subscribing to node events.
However, filtering by relationship events is an even more powerful logic. This is because these filters can also express the expected relationship field, or the expected concrete type at the other end of the relationship, provided that they are connected to abstract types.
Note that each relationship field specified is combined with the others using a logical OR
.
Only events matching these relationship field names are returned in the subscription.
You can further filter each relationship field by node and relationship properties.
These fields are combined in the resulting filter with a logical AND
.
As an example, in the following type definitions:
type Movie {
title: String
genre: String
actors: [Actor!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN)
}
interface ActedIn @relationshipProperties {
screenTime: Int!
}
type Actor {
name: String
}
The format of the where
argument is:
{
movie: {
# top-level properties of the node targeted for the subscription operation, supports operators
title_IN: ["The Matrix", "Fight Club"]
},
createdRelationship: {
actors: { # field name corresponding to a relationship in the type definition of the node targeted for the subscription operation
edge: {
# properties of the relationship, supports operators
screenTime_GT: 10,
},
node: {
# top-level properties of the node on the other end of the relationship, supports operators
name_STARTS_WITH: "Brad"
}
}
}
}
The following sections feature examples of how filtering can be applied when creating a subscription to relationship events.
Newly created relationship
The following example filters the subscriptions to newly created relationships that are connecting a Movie
from genres other than "Drama", and to an Actor
with a screen time bigger than 10 minutes:
subscription {
movieRelationshipCreated(where: { movie: { NOT: { genre: "Drama" } }, createdRelationship: { actors: { edge: { screenTime_GT: 10 } } } }) {
movie {
title
}
createdRelationship {
actors {
screenTime
node {
name
}
}
}
}
}
The |
Newly deleted relationship
The following example filters the subscriptions to deleted relationships that were connecting a Movie
of the genre "Comedy"
or "Adventure"
to an Actor
named "Jim Carrey"
:
subscription {
movieRelationshipDeleted(where: { movie: { genre_IN: ["Comedy", "Adventure"] }, createdRelationship: { actors: { node: { name: "Jim Carrey" } } } }) {
movie {
title
}
deletedRelationship {
actors {
screenTime
node {
name
}
}
}
}
}
The |
Relationship-related filters
In addition to filtering node or relationship properties, the relationship-related filtering logic is even more powerful. This is because these filters can also express the expected relationship field, or the expected concrete type at the other end of the relationship, provided that they are connected to abstract types.
The following examples are valid for both CREATE_RELATIONSHIP
and DELETE_RELATIONSHIP
events.
Their purpose is to illustrate the various ways in which a subscription to a relationship event can be filtered.
Considering the following type definitions:
type Movie {
title: String
genre: String
actors: [Actor!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN)
directors: [Director!]! @relationship(type: "DIRECTED", properties: "Directed", direction: IN)
reviewers: [Reviewer!]! @relationship(type: "REVIEWED", properties: "Review", direction: IN)
}
interface ActedIn @relationshipProperties {
screenTime: Int!
}
type Actor {
name: String
}
type Person implements Reviewer {
name: String
reputation: Int
}
union Director = Person | Actor
interface Directed @relationshipProperties {
year: Int!
}
interface Reviewer {
reputation: Int!
}
type Magazine implements Reviewer {
title: String
reputation: Int!
}
interface Review @relationshipProperties {
score: Int!
}
And the base subscription operation:
subscription MovieRelationshipDeleted($where: MovieRelationshipDeletedSubscriptionWhere) {
movieRelationshipDeleted(where: $where) {
movie {
title
}
deletedRelationship {
actors {
screenTime
node {
name
}
}
directors {
year
node {
... on PersonEventPayload { # generated type
name
reputation
}
... on ActorEventPayload { # generated type
name
}
}
}
reviewers {
score
node {
reputation
... on MagazineEventPayload { # generated type
title
reputation
}
... on PersonEventPayload { # generated type
name
reputation
}
}
}
}
}
}
You can use the following where
inputs in the GraphQL variable values to get different results:
Filtering via implicit/explicit declaration
Implicit or explicit declaration is used to filter specific relationship types that are expected to be returned to a subscription.
For example, when subscribing to created or deleted relationships to a Movie
, a user might only be interested in the relationship of type ACTED_IN
, but indifferent to the properties of the Actor
node or the other relationships connected to it.
In this case, the corresponding field name of this relationship is actors
.
By explicitly specifying the actors
field name, you can filter-out events to other relationship properties:
{
where: {
deletedRelationship: {
actors: {} # no properties specified here, therefore all relationships to this field name will be returned
}
}
}
In case you are interested in Actor
nodes conforming to some filters, for example with the name starting with the letter "A", the procedure is no different than subscribing to node events:
{
where: {
deletedRelationship: {
actors: {
node: { # use operations to specify filers on the top-level properties of the node at the other end of the relationship
name_STARTS_WITH: "A"
}
}
}
}
}
If you are also interested in the relationship itself conforming to some filters, such as the Actor
having spent no more than 40 minutes in the Movie
, this is how the query may look:
{
where: {
deletedRelationship: {
actors: {
edge: { # use operations to specify filers on the top-level properties of the relationship
screenTime_LT: 40,
}
node: {
name: "Alvin"
}
}
}
}
}
Multiple relationship types can also be included in the returned subscriptions by explicitly specifying the corresponding field names. For instance:
{
where: {
deletedRelationship: {
actors: {}, # include all relationships corresponding of type `ACTED_IN`
directors: {} # include all relationships corresponding of type `DIRECTED`
# exclude relationships of type `REVIEWED`
}
}
}
Now, if you are interested in all relationship types, you can either express this implicitly by not specifying any:
{
where: {
deletedRelationship: {} # include all relationships of all types
}
}
Or explicitly by specifying the field names of all the relationships connected to the type targeted for the subscription:
{
where: {
deletedRelationship: {
# include all relationships of all types
# subscription target type is `Movie`, which has the following relationship field names:
actors: {},
directors: {},
reviewers: {}
}
}
}
Note, however, that as any filters are applied to any of the relationships, explicitly including those that you are interested in subscribing to is a mandatory step.
For example, if all relationships should be returned, but you want to filter-out the REVIEWED
ones which have a score lower than 7, this is how your query may look like:
{
where: {
deletedRelationship: {
actors: {}, # include all relationships of type `ACTED_IN`
directors: {}, # include all relationships of type `DIRECTED`
reviewers: { # include all relationships of type `REVIEWED`, with the score property greater than 7
edge: {
score_GT: 7
}
}
}
}
}
Different filters can also be applied to different relationships without any constraints. For example:
{
where: {
deletedRelationship: {
actors: { # include some relationships of type `ACTED_IN`, filtered by relationship property `screenTime` and node property `name`
edge: {
screenTime_LT: 60,
},
node: {
name_IN: ["Tom Hardy", "George Clooney"]
}
},
directors: {}, # include all relationships of type `DIRECTED`
reviewers: { # include some relationships of type `REVIEWED`, filtered by relationship property `score` only
edge: {
score_GT: 7
}
}
}
}
}
In the previous example, there is an implicit logical |
There is an implicit logical |
Abstract types
The following sections describe how to filter subscriptions using abstract types.
Union type
This example illustrates how to filter the node at the other end of the relationship when it is of a union type:
{
where: {
deletedRelationship: {
directors: { # relationship to a union type
Person: { # concrete type that makes up the union type
edge: {
year_GT: 2010
},
node: {
name: "John Doe",
reputation: 10
}
},
Actor: { # concrete type that makes up the union type
edge: {
year_LT: 2005
},
node: {
name: "Tom Hardy"
}
}
},
}
}
}
The result is that only relationships of type DIRECTED
are returned to the subscription, where the Director
is a Person
named "John Doe"
, who directed the movie after 2010, or where the Director
is an Actor
named "Tom Hardy"
who directed the movie before 2005.
Note that the relationship field name is split into multiple sections, one for each of the concrete types that make up the union type. The relationship properties do not exist outside the confines of one of these sections, even though the properties are the same.
Now, take the other example that did not explicitly specify the concrete types:
{
where: {
deletedRelationship: {
directors: {}, # include all relationships of type `DIRECTED`
}
}
}
Following the same logic as for the relationship field names: when nothing is explicitly provided, then all is accepted.
Thus relationships of type DIRECTED
, established between a Movie
and any of the concrete types that make up the union type Director
are returned to the subscription.
It is equivalent to the following:
{
where: {
deletedRelationship: {
directors: { # include all relationships of type `DIRECTED`
Actor: {},
Person: {}
}
}
}
}
Note that explicitly specifying a concrete type excludes the others from the returned events:
{
where: {
deletedRelationship: {
directors: {
Actor: {} # include all relationships of type `DIRECTED` to an `Actor` type
}
}
}
}
In this case, only relationships of type DIRECTED
between a Movie
and an Actor
are returned to the subscription.
Those between a Movie
and a Person
are filtered out.
One reason why this might be done is to include some filters on the Actor
type:
{
where: {
deletedRelationship: {
directors: {
Actor: { # include some relationships of type `DIRECTED` to an `Actor` type, that conform to the filters
node: {
NOT: { name: "Tom Hardy" }
}
}
}
}
}
}
To include filters on the Actor
type, but also include the Person
type in the result, you need to make the intent explicit:
{
where: {
deletedRelationship: {
directors: {
Actor: { # include some relationships of type `DIRECTED` to an `Actor` type, that conform to the filters
node: {
NOT: { name: "Tom Hardy" }
}
},
Person: {} # include all relationships of type `DIRECTED` to a `Person` type
}
}
}
}
Interface type
The following example illustrates how to filter the node at the other end of the relationship when it is of an interface type:
{
where: {
deletedRelationship: {
reviewers: { # relationship to an interface type
edge: {
# relationship properties of a relationship of type `REVIEWED`
score_GT: 7
},
node: {
# common fields declared by the interface
reputation_GTE: 8
_on: { # specific fields depending on the concrete type
Person: { # concrete type that makes up the interface type
name: "Jane Doe",
reputation_GTE: 7
},
Magazine: { # concrete type that makes up the interface type
title_IN: ["Sight and Sound", "Total Film"],
reputation_LT: 9
}
}
}
},
}
}
}
This example returns events for relationships between the type Movie
and Reviewer
, where the score is higher than 7, and the Reviewer
is a Person
named "Jane Doe", with a reputation greater or equal to 7, or the Reviewer
is a Magazine
with the reputation of 8.
Note how the reputation field is part of the interface type, and can thus be specified in 3 ways: inside the |
To get all relationships of type REVIEWED
with a certain score returned, you can use implicit filtering, such as:
{
where: {
deletedRelationship: {
reviewers: {
edge: { # include some relationships of type `REVIEWED` to both `Person` and `Magazine` Concrete types, that conform to the filters
score: 10
},
},
}
}
}
Implicit filtering can still be used, even for relationships of type REVIEWED
to a Reviewer
of a specific reputation:
{
where: {
deletedRelationship: {
reviewers: {
node: { # include some relationships of type `REVIEWED` to both `Person` and `Magazine` Concrete types, that conform to the filters
reputation: 9
}
},
}
}
}
It is only when a specific concrete type needs to be filtered that you need to be explicit in the concrete types that you are interested in:
{
where: {
deletedRelationship: {
reviewers: {
node: {
_on: {
Person: { # include some relationships of type `REVIEWED` to Concrete type `Person`, that conform to the filters
name: "Jane Doe",
reputation_GTE: 9
},
}
}
},
}
}
}
This example does not include relationships of type REVIEWED
to the Magazine
type.
You can do that by making them explicit:
{
where: {
deletedRelationship: {
reviewers: {
node: {
_on: {
Person: { # include some relationships of type `REVIEWED` to Concrete type `Person`, that conform to the filters
name: "Jane Doe",
reputation_GTE: 9
},
Magazine: {} # include all relationships of type `REVIEWED` to Concrete type `Magazine`
}
}
},
}
}
}