Skip to content

Commit

Permalink
Updated Extending Filtering Docs (#5996)
Browse files Browse the repository at this point in the history
  • Loading branch information
AntonC9018 authored Mar 28, 2023
1 parent 7455e2c commit 16ca172
Showing 1 changed file with 43 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ default settings, are all filter operations that work over `IQueryable` on all d
Sometimes this is not enough. Some databases might not support `IQueryable`. Some other databases may have
technology-specific operations (e.g. SQL Like). Filtering was designed with extensibility in mind.

Filtering can be broken down into two basic parts. Schema building and execution. In schema building,
Filtering can be broken down into two basic parts: schema building and execution. In schema building,
the input types are created. In execution, the data passed by the user is analyzed and translated to a
database query. Both parts can be configured over a convention.

In theory, you are free to design the structure of filters as it suits you best.
Usually, it makes sense to divide the structure into two parts. The _field_ and the _operation_.

The query below returns all movies where the franchise is equal to "Star Wars". The _field_ `franchise` where the filter
is applied to and the _operation_ equals (`eq`) that should operate on this field.
The query below returns all movies where the franchise is equal to "Star Wars". There are the _field_ `franchise` that the filter
applies to and the _operation_ equals (`eq`) that should operate on this field.

```graphql
{
Expand All @@ -27,8 +27,8 @@ is applied to and the _operation_ equals (`eq`) that should operate on this fiel
}
```

Fields can also form paths. In the query below there are two _fields_ `genre` and `totalMovieCount` and one operation equals
`eq`
Fields can also form paths. In the query below there are two _fields_: `genre` and `totalMovieCount`, and one operation equals
`eq`.

```graphql
{
Expand All @@ -38,43 +38,44 @@ Fields can also form paths. In the query below there are two _fields_ `genre` an
}
```

The two queries above show the difference between _fields_ and _operations_ well. A field is always context-specific.
Even when two fields have the same name, like the description of a movie and the description of a genre, they have different meanings.
One field refers to the description of a movie and the other description refers to the description of a genre.
Same name, different meanings. An operation on the other hand, has always the same meaning.
The equals operation (`eq`) do always mean that the value of the selected field, should
be equals to the value that was provided in the query.
Operations can be applied in different contexts, but the operation itself, stays the same.
The name of the operation should be consistent. There should only be one operation that checks for equality.
This operation should always have the same name.
The difference between _fields_ and _operations_ is illustrated by the two queries above.
A field is always context-specific, meaning that even when two fields have the same name, such as "description,"
they may refer to different things, such as the description of a movie or the description of a genre in the examples above

With this in mind, we can have a deeper dive into filtering. Buckle up, this might get exciting.
On the other hand, an operation always has the same meaning.
The "equals" operation (`eq`) always means that the value of the selected field should be equal to the value provided in the query.
Operations can be applied in different contexts, but the operation itself remains the same.
The name of the operation should be consistent, and there should only be one operation that checks for equality.

With this in mind, let's take a deeper dive into filtering. Buckle up; it might get exciting.

# How everything fits together

At the core of the configuration API of filtering there sits a convention. The convention holds the whole
configuration that filtering needs to create filter types and to translate them to the database.
During schema creation, the schema builder asks the convention how the schema should look like.
The convention defines the names and descriptions of types and fields and also what the type should be used for properties.
At the core of the configuration API of filtering there sits a convention.
The convention holds the entire configuration that filtering needs to create filter types and translate them to database queries.
During schema creation, the schema builder asks the convention how the schema should look.
The convention defines the names and descriptions of types and fields, what types should be used for properties.
The convention also defines what provider should be used to translate a GraphQL query to a database query.
The provider is the only thing that is used after the schema is built.
Every field or operation in a filter type has a handler annotated.
During schema initialization, these handlers are bound, to the GraphQL fields. The provider can specify which handler should be bound to which field.

Every field or operation in a filter type has an annotated handler.
During schema initialization, these handlers are bound to the GraphQL fields.
The provider can specify which handler should be bound to which field.
During execution, the provider visits the incoming value node and executes the handler on the fields.
This loose coupling allows defining the provider independently of the convention.

# Filter Convention

A filter convention is a dotnet class that has to implement the interface `IFilterConvention`.
Instead of writing a convention completely new, it is recommended to extend the base convention `FilterConvention`
This convention is also configurable with a fluent interface, so in most cases you can probably just use the descriptor API.
This convention is also configurable with a fluent interface, so in most cases, you can probably just use the descriptor API.

## Descriptor

Most of the capabilities of the descriptor are already documented under `Fetching Data -> Filtering`.
If you have not done this already, it is now the right time to head over to [Filtering](/docs/hotchocolate/v12/fetching-data/filtering) and read the parts about the `FilterConventions`
If you have not already done so, now is the right time to head over to [Filtering](/docs/hotchocolate/v12/fetching-data/filtering) and read the parts about the `FilterConventions`

There are two things on this descriptor that are not documented in `Fetching Data`:
There are two things on this descriptor that are not documented in `Fetching Data`: operation and provider.

### Operation

Expand All @@ -92,11 +93,11 @@ conventionDescriptor
.Name("equals")
.Description("Compares the value of the input to the value of the field");
```

With this configuration, all equals operations are now no longer names `eq` but `equals` and have a description.

If you want to create your own operations, you have to choose an identifier.
To make sure to not collide with the framework, choose a number that is higher than 1024.
To make sure not to collide with the framework, choose a number that is higher than 1024.
If you are a framework developer and want to create an extension for HotChocolate, talk to us.
We can assign you a range of operations so you do not collide with the operations defined by users.

Expand Down Expand Up @@ -135,17 +136,18 @@ To apply this configuration to operations types, you can use the Configure metho
IFilterConventionDescriptor Provider(Type provider);
```

On the convention, you can also specify what provider should be used. For now you need just to know
that you can configure the provider here. We will have a closer look at the provider later.
On the convention, you can also specify what provider should be used.
For now, you only need to know that you can configure the provider here.
We will have a closer look at the provider later.

```csharp
conventionDescriptor.Provider<CustomProvider>();
```

## Custom Conventions

Most of the time the descriptor API should satisfy your needs. It is recommended to build extensions
based on the descriptor API, rather than creating a custom convention.
Most of the time the descriptor API should satisfy your needs.
It is recommended to build extensions based on the descriptor API rather than creating a custom convention.
However, if you want to have full control over naming and type creation, you can also override the methods
you need on the `FilterConvention`.

Expand All @@ -167,8 +169,10 @@ public class CustomConvention : FilterConvention
# Providers

Like the convention, a provider can be configured over a fluent interface.
Every filter field or operation has a specific handler defined. The handler translates the operation to the database.
These handlers are stored on the provider. After the schema is initialized, an interceptor visits the filter types and requests a handler from the provider.
Every filter field or operation has a specific handler defined.
The handler translates the operation to the database.
These handlers are stored on the provider.
After the schema is initialized, an interceptor visits the filter types and requests a handler from the provider.
The handler is annotated directly on the field.
The provider translates an incoming query into a database query by traversing an input object and executing the handlers on the fields.

Expand All @@ -177,7 +181,7 @@ In case, of MongoDB this is a `FilterDefinition`. Provider, visitor context and

To inspect and analyze the input object, the provider uses a visitor.

What a visitor is and how you can write you own visitor is explained [here](/docs/hotchocolate/v12/api-reference/visitors)
What a visitor is and how you can write one is explained [here](/docs/hotchocolate/v12/api-reference/visitors)

Visitors are a powerful yet complex concept, we tried our best to abstract it away.
For most cases, you will not need to create a custom visitor.
Expand Down Expand Up @@ -259,15 +263,15 @@ Field handlers can push data on this context, to make it available for other han
The context contains `Types`, `Operations`, `Errors` and `Scopes`. It is very provider-specific what data you need to store in the context.
In the case of the `IQueryable` provider, it also contains `RuntimeTypes` and knows if the source is `InMemory` or a database call.

With `Scopes` it is possible to add multiple logical layers to a context. In the case of `IQuerable` this is needed, whenever a new closure starts
With `Scopes` it is possible to add multiple logical layers to a context. In the case of `IQuerable` this is needed, whenever a new closure starts:

```csharp
// /------------------------ SCOPE 1 -----------------------------\
// /----------- SCOPE 2 -------------\
users.Where(x => x.Company.Addresses.Any(y => y.Street == "221B Baker Street"))
```

A filter statement that produces the expression above would look like this
A filter statement that produces the expression above would look like this:

```graphql
{
Expand All @@ -281,7 +285,7 @@ A filter statement that produces the expression above would look like this
}
```

A little simplified this is what happens during visitation:
To make it simpler, this is roughly what occurs during the visitation:

```graphql
{
Expand Down Expand Up @@ -340,10 +344,10 @@ The default filtering implementation uses `IQueryable` under the hood. You can c
The following example creates a `StringOperationHandler` that supports case insensitive filtering:

```csharp
// The QueryableStringOperationHandler already has an implemenation of CanHandle
// The QueryableStringOperationHandler already has an implementation of CanHandle
// It checks if the field is declared in a string operation type and also checks if
// the operation of this field uses the `Operation` specified in the override property further
// below
// the operation of this field uses the `Operation` specified in the override property
// further below
public class QueryableStringInvariantEqualsHandler : QueryableStringOperationHandler
{
// For creating a expression tree we need the `MethodInfo` of the `ToLower` method of string
Expand Down

0 comments on commit 16ca172

Please sign in to comment.