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.

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`
                    }
                }
            },
        }
    }
}