Filtering

Operators

When querying for data, a number of operators are available for different types in the where argument of a Query or Mutation.

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), Temporal Types and Spatial Types:

  • _LT

  • _LTE

  • _GTE

  • _GT

Filtering of spatial types is different to filtering of numerical types and also offers an additional filter - see Spatial Types.

String comparison

The following case-sensitive comparison operators are only available for use on String and ID types:

  • _STARTS_WITH

  • _ENDS_WITH

  • _CONTAINS

The following operators are disabled by default:

  • _LT

  • _LTE

  • _GT

  • _GTE

They can be enabled by explicitly adding them in the features options:

const { Neo4jGraphQL } = require("@neo4j/graphql");
const neo4j = require("neo4j-driver");

const typeDefs = `
    type User {
        name: String
    }
`;

const driver = neo4j.driver(
    "bolt://localhost:7687",
    neo4j.auth.basic("neo4j", "password")
);

const features = {
    filters: {
        String: {
            LT: true,
            GT: true,
            LTE: true,
            GTE: true
        }
    }
};

const neoSchema = new Neo4jGraphQL({ features, typeDefs, driver });

RegEx matching

The filter _MATCHES is also available for comparison of String and ID types, which accepts a RegEx string as an argument and returns any matches. Note that RegEx matching filters are disabled by default.

To enable the inclusion of this filter, set the config option enableRegex to true.

The nature of RegEx matching means that on an unprotected API, this could potentially be used to execute a ReDoS attack (https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS) against the backing Neo4j database.

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, NOT operators

Complex combinations of operators are possible using the AND/ OR / NOT operators. These are stand-alone operators - that is, they are used as such and not appended to field names, and they accept an array/object argument with items of the same format as the where argument.

Usage

Using the type definitions from Queries, below are some example of how filtering can be applied when querying for data.

At the root of a Query

By using the where argument on the Query field in question, you can return a User with a particular ID:

query {
    users(where: { id: "7CF1D9D6-E527-4ACD-9C2A-207AE0F5CB8C" }) {
        name
    }
}

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. As an example, the below query matches all actors by the name of either "Keanu" or not belonging to the "Pantoliano" family, that played in "The Matrix" movie.

query {
    actors(where: {
        AND: [
            {
                OR: [
                    { name_CONTAINS: "Keanu" },
                    { NOT: { name_ENDS_WITH: "Pantoliano" } }
                ]
            },
            {
                movies_SOME: { title: "The Matrix" }
            }
        ]}
    ) {
        name
        movies {
            title
        }
    }
}

Filtering relationships

By using the where argument on a relationship field, you can filter for a Post with a particular ID across all Users:

query {
    users {
        id
        name
        posts(where: { id: "2D297425-9BCF-4986-817F-F06EE0A1D9C7" }) {
            content
        }
    }
}

Relationship Filtering

For each relationship field, field, a set of filters are available depending on whether the relationship is n..1 or n..m. In the case of n..1, filtering is done on equality or inequality of the related node by specifying a filter on field, respectively. In the case of n..m, filtering is done on the list of related nodes and is based on the List Predicates available in Cypher.

Available Filters

n..1
  • field - equality

n..m

Relationship Filtering Usage Examples

For this section take as type definitions the following:

type User {
    id: ID!
    name: String
    posts: [Post!]! @relationship(type: "HAS_POST", direction: OUT)
}

type Post {
    id: ID!
    content: String
    author: User! @relationship(type: "HAS_POST", direction: IN)
    likes: [User!]! @relationship(type: "LIKES", direction: IN)
}

n..1 Relationships

In the above, an author represents a n..1 relationship on Post where a given Post is authored by one, and only one, author. The available filters here will be author.

Find all posts by a desired author

query {
    posts(where: { author: { id: "7CF1D9D6-E527-4ACD-9C2A-207AE0F5CB8C" } }) {
        content
    }
}

Find all posts not by an undesired author

query {
    posts(where: { NOT: { author: { id: "7CF1D9D6-E527-4ACD-9C2A-207AE0F5CB8C" } } }) {
        content
    }
}

n..m Relationships

In the above, posts represents a n..m relationship on User where a given User can have any number of posts.

Find all users where all of their posts contain search term: "neo4j"

query {
    users(where: { posts_ALL: { content_CONTAINS: "neo4j" } }) {
        name
    }
}

Find all users where none of their posts contain search term: "cypher"

query {
    users(where: { posts_NONE: { content_CONTAINS: "cypher" } }) {
        name
    }
}

Find all users where some of their posts contain search term: "graphql"

query {
    users(where: { posts_SOME: { content_CONTAINS: "graphql" } }) {
        name
    }
}

Find all users where only one of their posts contain search term: "graph"

query {
    users(where: { posts_SINGLE: { content_CONTAINS: "graph" } }) {
        name
    }
}

Aggregation Filtering

This library offers, for each relationship, an aggregation key inside the where argument. You can use the aggregation key to satisfy questions such as:

  • Find the posts where the number of likes are greater than 5

  • Find flights where the average age of passengers is greater than or equal to 18

  • Find movies where the shortest actor screen time is less than 10 minutes

You can use this where aggregation on both the node and edge of a relationship.

Aggregation Filtering Usage Examples

Find the posts where the number of likes are greater than 5

Given the schema:

type User {
    name: String
}

type Post {
    content: String
    likes: [User!]! @relationship(type: "LIKES", direction: IN)
}

Answering the question:

query {
    posts(where: { likesAggregate: { count_GT: 5 } }) {
        content
    }
}

Find flights where the average age of passengers is greater than or equal to 18

Given the schema:

type Passenger {
    name: String
    age: Int
}

type Flight {
    code: String
    passengers: [Passenger!]! @relationship(type: "FLYING_ON", direction: IN)
}

Answering the question:

query {
    flights(where: { passengersAggregate: { node: { age_AVERAGE_GTE: 18 } } }) {
        code
    }
}

Find movies where the shortest actor screen time is less than 10 minutes

Given the schema:

type Movie {
    title: String
    actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn")
}

type Person {
    name: String
}

interface ActedIn {
    screenTime: Int
}

Answering the question:

query {
    movies(where: { actorsAggregate: { edge: { screenTime_MIN_LT: 10 } } }) {
        title
    }
}

Aggregation Filtering Operators

Below you will learn more about the autogenerated filters available on the aggregate key and for each type on the node and edge of the specified relationship.

Count

This is a special 'top level' key inside the where aggregation and will be available for all relationships. This is used to count the amount of relationships the parent node is connected to. The operators count has are as follows:

  • count_EQUAL

  • count_GT

  • count_GTE

  • count_LT

  • count_LTE

Example
query {
    posts(where: { likesAggregate: { count_GT: 5 } }) {
        content
    }
}

ID

No Aggregation filters are available for ID.

String

Fields of type String have the following operators:

  • _AVERAGE_LENGTH_EQUAL

  • _AVERAGE_LENGTH_GT

  • _AVERAGE_LENGTH_GTE

  • _AVERAGE_LENGTH_LT

  • _AVERAGE_LENGTH_LTE

  • _SHORTEST_LENGTH_EQUAL

  • _SHORTEST_LENGTH_GT

  • _SHORTEST_LENGTH_GTE

  • _SHORTEST_LENGTH_LT

  • _SHORTEST_LENGTH_LTE

  • _LONGEST_LENGTH_EQUAL

  • _LONGEST_LENGTH_GT

  • _LONGEST_LENGTH_GTE

  • _LONGEST_LENGTH_LT

  • _LONGEST_LENGTH_LTE

These operators are calculated against the length of each string.

Example
query {
    posts(where: { likesAggregate: { node: { name_LONGEST_LENGTH_GT: 5 } } }) {
        content
    }
}

Numerical Types

Numerical types include the following:

  • Int

  • Float

  • BigInt

The types in the list above have the following operators:

  • _AVERAGE_EQUAL

  • _AVERAGE_GT

  • _AVERAGE_GTE

  • _AVERAGE_LT

  • _AVERAGE_LTE

  • _SUM_EQUAL

  • _SUM_GT

  • _SUM_GTE

  • _SUM_LT

  • _SUM_LTE

  • _MIN_EQUAL

  • _MIN_GT

  • _MIN_GTE

  • _MIN_LT

  • _MIN_LTE

  • _MAX_EQUAL

  • _MAX_GT

  • _MAX_GTE

  • _MAX_LT

  • _MAX_LTE

Example
query {
    movies(where: { actorsAggregate: { edge: { screenTime_MIN_LT: 10 } } }) {
        title
    }
}

Temporal Types

Temporal types include the following:

  • DateTime

  • LocalDateTime

  • LocalTime

  • Time

  • Duration

The types listed above have the following aggregation operators:

  • _MIN_EQUAL

  • _MIN_GT

  • _MIN_GTE

  • _MIN_LT

  • _MIN_LTE

  • _MAX_EQUAL

  • _MAX_GT

  • _MAX_GTE

  • _MAX_LT

  • _MAX_LTE

Whilst the Duration type also has the following additional operators:

  • _AVERAGE_EQUAL

  • _AVERAGE_GT

  • _AVERAGE_GTE

  • _AVERAGE_LT

  • _AVERAGE_LTE