diff --git a/modules/ROOT/pages/queries-aggregations/filtering.adoc b/modules/ROOT/pages/queries-aggregations/filtering.adoc index 4f56ddbe..d1c62515 100644 --- a/modules/ROOT/pages/queries-aggregations/filtering.adoc +++ b/modules/ROOT/pages/queries-aggregations/filtering.adoc @@ -3,12 +3,51 @@ :page-aliases: filtering.adoc :description: This page describes filtering operators. -When querying for data, a number of operators are available for different types in the `where` argument of a query or mutation. +When querying for data, a number of operators are available for the types in the `where` argument of a query or mutation, allowing you to filter query results or specify the set of objects a mutation applies to. -== Equality operators +Operators can either be standalone operators (see xref:#_boolean_operators[]) or they are appended to field names (for example, xref:/queries-aggregations/filtering.adoc#_string_comparison[]). + +All operators can be combined using the Boolean operators `AND`, `OR`, and `NOT`. + +== Operators + +=== Boolean operators + +As standalone operators, Boolean operators accept an array argument with items of the same format as the `where` argument. +This way, they can be nested to form complex Boolean expressions. + +For example, if you want to match all actors either by the name of "Keanu" or not belonging to the "Pantoliano" family, who played in "The Matrix" movie, here is how you query that: + +[source, graphql, indent=0] +---- +query { + actors(where: { + AND: [ + { + OR: [ + { name_CONTAINS: "Keanu" }, + { NOT: { name_ENDS_WITH: "Pantoliano" } } + ] + }, + { + movies_SOME: { title: "The Matrix" } + } + ]} + ) { + name + movies { + title + } + } +} +---- + +`name_CONTAINS` and `name_ENDS_WITH` are xref:/queries-aggregations/filtering.adoc#_string_comparison[String comparisons] while `movies_SOME` is a xref:/queries-aggregations/filtering.adoc#_relationship_filtering[relationship filter]. + +=== Equality operators + +All types can be tested for equality or non-equality. -All types can be tested for either equality. -For non-equality, you *must* use the xref:/queries-aggregations/filtering.adoc#_logical_operators[`NOT`] logical operator. For example: .Filtering all users named John @@ -21,12 +60,24 @@ query { } ---- +For non-equality, you must use the xref:/queries-aggregations/filtering.adoc#_boolean_operators[`NOT`] logical operator. + +.Filtering all users which are not named John +[source, graphql, indent=0] +---- +query { + users(where: { NOT: {name: "John" }}) + id + name + } +---- + [NOTE] ==== For the `Boolean` type, equality operators are the only ones available. ==== -== Numerical operators +=== Numerical operators These are the operators available for numeric (`Int`, `Float`, xref::/types/scalar.adoc[`BigInt`]), xref::/types/temporal.adoc[temporal] and xref::/types/spatial.adoc[spatial] types: @@ -52,43 +103,72 @@ query { Spatial types use numerical filtering differently and they also have additional options. See xref:filtering.adoc#_filtering_spatial_types[Filtering spatial types] for more information. -== Logical operators +==== Spatial type filtering -All operators can be combined using the logical operators `AND`, `OR`, and `NOT`. -They can also be standalone operators, which means that they can be used as such and not be appended to field names. +Both the `Point` and the `CartesianPoint` types use xref::queries-aggregations/filtering.adoc#_numerical_operators[numerical operators] and have an additional `_DISTANCE` filter. +Here is a list of what each filter does for the two types: -These operators 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. +* `_LT`: checks if a point is less than the distance in the `distance` field away (in meters) from the point specified by the `point` field. +* `_LTE`: checks if a point is less than or equal to the distance in the `distance` field away (in meters) from the point specified by the `point` field. +* `_DISTANCE`: checks if a point is the exact distance in the `distance` field away (in meters) from the point specified by the `point` field. +* `_GT`: checks if a point is greater than the distance in the `distance` field away (in meters) from the point specified by the `point` field. +* `_GTE`: checks if a point is greater than or equal to the distance in the `distance` field away (in meters) from the point specified by the `point` field. -For example, if you want to match all actors by the name of either "Keanu" or not belonging to the "Pantoliano" family, that played in "The Matrix" movie, here is how you can query that: +For a `Point` type, all filters take the following type as an argument: [source, graphql, indent=0] ---- -query { - actors(where: { - AND: [ - { - OR: [ - { name_CONTAINS: "Keanu" }, - { NOT: { name_ENDS_WITH: "Pantoliano" } } - ] - }, - { - movies_SOME: { title: "The Matrix" } - } - ]} - ) { +input PointDistance { + point: Point! + distance: Float! +} +---- + +In practice, you can construct queries like the following, which finds all users within a 5km (5000m) radius of a `Point`: + +[source, graphql, indent=0] +---- +query CloseByUsers($longitude: Float!, $latitude: Float!) { + users(where: { location_LTE: { point: { longitude: $longitude, latitude: $latitude }, distance: 5000 } }) { name - movies { - title + location { + longitude + latitude } } } ---- +Similarly, for a `CartesianPoint` type, all filters take the following type as an argument: -== String comparison +[source, graphql, indent=0] +---- +input CartesianPointDistance { + point: CartesianPoint! + distance: Float! +} +---- -The following case-sensitive comparison operators are only available for use on `String` and `ID` types: +The same query for a `CartesianPoint`: + +[source, graphql, indent=0] +---- +query CloseByUsers($x: Float!, $y: Float!) { + users(where: { location_LTE: { point: { x: $x, y: $y }, distance: 5000 } }) { + name + location { + x + y + } + } +} +---- + +== Type comparison + +=== String comparison + +The following case-sensitive comparison operators are available for `String` and `ID` types: * `_STARTS_WITH` * `_ENDS_WITH` @@ -141,16 +221,17 @@ const features = { const neoSchema = new Neo4jGraphQL({ features, typeDefs, driver }); ---- -== RegEx matching +=== RegEx matching -The filter `_MATCHES` is also available for comparison of `String` and `ID` types. +The filter `_MATCHES` is available for comparison of `String` and `ID` types. It accepts RegEx strings as an argument and returns any matches. - -Note that RegEx matching filters are **disabled by default**. +Note that RegEx matching filters are disabled by default. This is because, on an unprotected API, they could potentially be used to execute a https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS[ReDoS attack^] against the backing Neo4j database. -If you want to enable them, set the features configuration object for each: +If you want to enable RegEx matching, update the `features` configuration object. + +For `String`: [source, javascript, indent=0] ---- @@ -172,8 +253,8 @@ For `ID`: ---- const features = { filters: { - String: { - ID: true, + ID: { + MATCHES: true, } } }; @@ -183,7 +264,6 @@ const neoSchema = new Neo4jGraphQL({ features, typeDefs, driver }); For both `String` and `ID`: - [source, javascript, indent=0] ---- const features = { @@ -200,96 +280,74 @@ const features = { const neoSchema = new Neo4jGraphQL({ features, typeDefs, driver }); ---- -== 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`. - -== Filtering spatial types +=== Array comparison -Both the `Point` and the `CartesianPoint` types use xref::queries-aggregations/filtering.adoc#_numerical_operators[numerical operators] and have an additional `_DISTANCE` filter. -Here is a list of what each filter does for the two types: - -* `_LT`: checks if a point is less than the distance in the `distance` field away (in meters) from the point specified by the `point` field. -* `_LTE`: checks if a point is less than or equal to the distance in the `distance` field away (in meters) from the point specified by the `point` field. -* `_DISTANCE`: checks if a point is the exact distance in the `distance` field away (in meters) from the point specified by the `point` field. -* `_GT`: checks if a point is greater than the distance in the `distance` field away (in meters) from the point specified by the `point` field. -* `_GTE`: checks if a point is greater than or equal to the distance in the `distance` field away (in meters) from the point specified by the `point` field. - -For a `Point` type, all filters take the following type as an argument: +Consider the following type definitions: [source, graphql, indent=0] ---- -input PointDistance { - point: Point! - distance: Float! +type Movie { + id: ID! + title: String! + genres: [String!] + year: Int! + actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) } ----- - -In practice, you can construct queries like the following, which finds all users within a 5km (5000m) radius of a `Point`: -[source, graphql, indent=0] ----- -query CloseByUsers($longitude: Float!, $latitude: Float!) { - users(where: { location_LTE: { point: { longitude: $longitude, latitude: $latitude }, distance: 5000 } }) { - name - location { - longitude - latitude - } - } +type Actor { + id: ID! + name: String! + movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) } ---- -Similarly, for a `CartesianPoint` type, all filters take the following type as an argument: +The `_IN` operator is available on non-array fields, and accepts an array argument: [source, graphql, indent=0] ---- -input CartesianPointDistance { - point: CartesianPoint! - distance: Float! +query { + movies(where: { year_IN: [1999, 2000, 2001] }) { + title + year + } } ---- -The same query for a `CartesianPoint`: +The query returns all movies released in the years 1999, 2000 and 2001. + +Conversely, the `_INCLUDES` operator is available on array fields, and accepts a single argument: [source, graphql, indent=0] ---- -query CloseByUsers($x: Float!, $y: Float!) { - users(where: { location_LTE: { point: { x: $x, y: $y }, distance: 5000 } }) { - name - location { - x - y - } +query { + movies(where: { genres_INCLUDES: "Action" }) { + title + genres } } ---- -== Querying an interface +The query returns all movies which have "Action" as one of their genres. + +`_IN` and `_INCLUDES` are available for all types except `Boolean`. + +== Interface filtering You can use the `typename_IN` filter to filter interfaces. Refer to xref:types/interfaces.adoc#type-definitions-interfaced-types-querying[Type definitions -> Type -> Interface] for more details and an example. == Relationship filtering -Relationship filtering depends on the type of relationship that you have: +Relationship filtering depends on the type of relationship: -* `n..1`: filtering done on equality or inequality of the related nodes by specifying a filter on `field`. -* `n..m`: filtering is done on the list of related nodes and is based on the https://neo4j.com/docs/cypher-manual/current/functions/predicate/[list predicates] available in Cypher: +* `n..1`: the filtering is done on equality or inequality of the related nodes by specifying a filter on `field`. +* `n..m`: the filtering is done on the list of related nodes and is based on the https://neo4j.com/docs/cypher-manual/current/functions/predicate/[list predicates] available in Cypher: ** `field_ALL` - https://neo4j.com/docs/cypher-manual/current/functions/predicate/#functions-all[all] ** `field_NONE` - https://neo4j.com/docs/cypher-manual/current/functions/predicate/#functions-none[none] ** `field_SOME` - https://neo4j.com/docs/cypher-manual/current/functions/predicate/#functions-any[any] ** `field_SINGLE` - https://neo4j.com/docs/cypher-manual/current/functions/predicate/#functions-single[single] -As an example, take these type definitions: +For example, take these type definitions: [source, graphql, indent=0] ---- @@ -306,10 +364,12 @@ type Post { likes: [User!]! @relationship(type: "LIKES", direction: IN) } ---- + === `n..1` relationships -An `author` represents an `n..1` relationship on `Post`, where a given `Post` is authored by one, and only one, `author`. -The available filters here will be `author`. +In the type definitions example, an `author` represents an `n..1` relationship on `Post`, where a given `Post` is authored by one, and only one, `author`. +The available filter is `author`. + For example: .Find all posts by a desired author @@ -334,8 +394,9 @@ query { === `n..m` relationships -In the previous example, `posts` represents a `n..m` relationship on `User`, where a given `User` can have any number of `posts`. -Here are some query examples: +In the type definitions example, `posts` represents an `n..m` relationship on `User`, where a given `User` can have any number of `posts`. + +For example: .Find all users where all of their posts contain search term: `"neo4j"` [source, graphql, indent=0] @@ -379,8 +440,8 @@ query { == Aggregation filtering -This library offers, for each relationship, an aggregation key inside the `where` argument. -It can be used both on the `node` and `edge` of a relationship. +The Neo4j GraphQL library offers an aggregation key inside the `where` argument of each relationship. +You can use it both on the `node` and `edge` of a relationship. Here are some examples on how to apply this kind of filtering: @@ -464,7 +525,7 @@ query { } ---- -=== Operators +=== With operators Aggregation filtering can also be done with operators. They provide autogenerated filters available for each type on the `node` and `edge` of the specified relationship.