Filtering
Filtering can only be applied at the root of the Subscription operation.
Aggregations are not supported on subscription types, so there is currently no way to apply filter on these fields. |
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 yet 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 two comparison operators are available on non-array fields, and accept an array argument:
-
_IN
Conversely, the following operators are available on array fields, and accept a single argument:
-
_INCLUDES
These four operators are available for all types apart from Boolean
.
AND, OR operators
Complex combinations of operators are possible using the AND
/ OR
operators.
AND
/OR
operators accept as argument an array of items of the same format as the where
argument.
Check out a usage example in the Combining operators section below.
Subscribing to node events
The where
argument allows for specifying filters on top-level properties of the targeted nodes.
Only events matching these properties and type will be returned to the subscription.
Considering the following type definitions:
type Movie {
title: String
genre: String
averageRating: Float
releasedIn: Int
}
Below are some example of how filtering can be applied when creating a subscription.
Create
We can filter our movies by their genre:
subscription {
movieCreated(where: {genre: "Drama"}) {
createdMovie {
title
}
}
}
This way, only newly created movies with the genre "Drama"
will trigger events to this subscription.
where will only filter by properties set at the moment of creation.
|
Update
We can filter our movies with the average rating bigger than 8:
subscription {
movieUpdate(where: {averageRating_GT: 8}) {
updatedMovie {
title
}
}
}
This way, we will only receive events triggered by movies with the average rating bigger than 8 being modified.
Where will only filter by existing properties before the update.
|
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 above subscription, these GraphQL operations will only be triggered for "The Matrix"
movie.
Delete
we can filter our movies by their genre with the NOT
filter:
subscription {
movieDeleted(where: { NOT: { genre: "Comedy" } }) {
deletedMovie {
title
}
}
}
This way, only deleted movies of all genres except for "Comedy"
will trigger events to this subscription.
Where will only filter by existing properties right before deletion.
|
Combining operators
All above-mentioned operators can be combined using the AND
/OR
/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.
Say we like comedy movies except for romantic comedies from early 2000, although our favorite movies are ones from the Matrix Trilogy. We could subscribe to any updates that we are interested in 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 for specifying filters on the top-level properties of the targeted nodes, and 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 described above, and the usage is very similar to the one in Subscribing to node events.
The relationship-related filtering logic is even more powerful, as filters can also express the expected relationship field, or the expected concrete type at the other end of the relationship when having relationships 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 will be returned in the Subscription.
You can further filter each relationship field by node and relationship properties. As per usual, these fields are combined in the resulting filter with a logical AND
.
Considering 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"
}
}
}
}
Below are some example of how filtering can be applied when creating a subscription to relationship events.
Create Relationship
The following example filters the subscriptions to newly created relationships that are connecting a Movie
from genres other than "Drama", to an Actor
with a screen time bigger than 10:
subscription {
movieRelationshipCreated(where: { movie: { NOT: { genre: "Drama" } }, createdRelationship: { actors: { edge: { screenTime_GT: 10 } } } }) {
movie {
title
}
createdRelationship {
actors {
screenTime
node {
name
}
}
}
}
}
where will only filter by properties set at the moment of creation.
|
Delete Relationship
The following example filters the subscriptions to deleted relationships that were connecting a Movie
of 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
}
}
}
}
}
Where will only filter by existing properties right before deletion.
|
Relationship-related filters
In addition to filtering on node or relationship properties, the relationship-related filtering logic is even more powerful, as filters can also express the expected relationship field, or the expected concrete type at the other end of the relationship when having relationships to Abstract Types.
The following examples are valid for both CREATE_RELATIONSHIP
/DELETE_RELATIONSHIP
events. Their purpose is to illustrate the various ways in which a subscription to a relationship event can be filtered in a variety of ways.
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 {
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
}
}
}
}
}
}
Given the above subscription, 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 on the specific relationship types that are expected to be returned to a subscription.
For example, when subscribing to created or deleted relationships to a Movie
we might only be interested in the relationship of type ACTED_IN
, indifferent to the properties of the Actor
node or of the relationship to it. Note that the corresponding field name of this relationship is actors
.
By explicitly specifying the actors
field name, we filter-out events to other relationship properties:
{
where: {
deletedRelationship: {
actors: {} # no properties specified here, therefore all relationships to this field name will be returned
}
}
}
If we were interested in Actor
nodes conforming to some filters, for example with the name starting with the letter "A", it is no different than when 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"
}
}
}
}
}
Or we could also be interested in the relationship itself conforming to some filters, like the Actor
to have spent no more than 40 minutes in the Movie
:
{
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 be included in the returned subscriptions by explicitly specifying the corresponding field names like so:
{
where: {
deletedRelationship: {
actors: {}, # include all relationships corresponding of type `ACTED_IN`
directors: {} # include all relationships corresponding of type `DIRECTED`
# exclude relationships of type `REVIEWED`
}
}
}
In case we are interested in all relationship types, we 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: {}
}
}
}
As soon as we want to apply any filter to any of the relationships, explicitly including those that we are interested in is mandatory |
For example if all relationships should be returned, but we want to filter-out the REVIEWED
ones with a score less than 7:
{
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 be applied to the different relationships without any constraints:
{
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
}
}
}
}
}
Note that in the above, there is an implicit logical OR between the actors , directors and reviewers , relationship fields. I.e. a relationship of either type ACTED_IN or of type DIRECTED or of type REVIEWED will trigger the subscription above.
|
Note that there is an implicit logical AND between the edge and node fields inside of the actors relationship field. I.e. a relationship of type ACTED_IN with the property screenTime less than 60 and a target node with name in ["Tom Hardy", "George Clooney"] will trigger the subscription.
|
Abstract Types
Union Type
The following example illustrates how to filter on 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. |
What about the example above 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
between a Movie
and any of the Concrete types that make up the Union type Director
will be returned to the subscription.
It is therefore equivalent to the following:
{
where: {
deletedRelationship: {
directors: { # include all relationships of type `DIRECTED`
Actor: {},
Person: {}
}
}
}
}
Of course, it follows that explicitly specifying a Concrete type will exclude the other 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
will be returned to the subscription, those between a Movie
and a Person
being 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 Person
type in the result, we 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 on 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
}
}
}
},
}
}
}
The above will return events for relationships between the type Movie
and Reviewer
, where the score is greater 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.
Notice how the reputation field is part of the Interface type, and can thus be specified in 3 ways: inside the node key, inside each Concrete type, or in both places. When specified in both places, the filter is composed with a logical AND . Type Person overrides the reputation_GTE operator so the final filter is reputation_GTE: 7 , while type Magazine composes the original operator so the final filter is the interval reputation_GTE: 8 && reputation_LT: 9 .
|
To get all relationships of type REVIEWED
with a certain score returned, we can make use of the implicit filtering like so:
{
where: {
deletedRelationship: {
reviewers: {
edge: { # include some relationships of type `REVIEWED` to both `Person` and `Magazine` Concrete types, that conform to the filters
score: 10
},
},
}
}
}
Even for relationships of type REVIEWED
to a Reviewer
of a specific reputation, we can still make use of the implicit filtering:
{
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 we need to be explicit in the Concrete types that we 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
},
}
}
},
}
}
}
The above will not include relationships of type REVIEWED
to the Magazine
type. We can include them by making the intent 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`
}
}
},
}
}
}
Was this page helpful?