# Source: https://docs.hypermode.com/dgraph/graphql/schema/directives/search.md

# Search and Filtering

> What search can you build into your GraphQL API? Dgraph builds search into the fields of each type, so searching is available at deep levels in a query

<Info>
  We're overhauling Dgraph's docs to make them clearer and more approachable. If
  you notice any issues during this transition or have suggestions, please
  [let us know](https://github.com/hypermodeinc/docs/issues).
</Info>

The `@search` directive tells Dgraph what search to build into your GraphQL API.

When a type contains an `@search` directive, Dgraph constructs a search input
type and a query in the GraphQL `Query` type. For example, if the schema
contains

```graphql
type Post {
    ...
}
```

then Dgraph constructs a `queryPost` GraphQL query for querying posts. The
`@search` directives in the `Post` type control how Dgraph builds indexes and
what kinds of search it builds into `queryPost`. If the type contains

```graphql
type Post {
    ...
    datePublished: DateTime @search
}
```

then it's possible to filter posts with a date-time search like:

```graphql
query {
    queryPost(filter: { datePublished: { ge: "2020-06-15" }}) {
        ...
    }
}
```

If the type tells Dgraph to build search capability based on a term (word) index
for the `title` field

```graphql
type Post {
    ...
    title: String @search(by: [term])
}
```

then, the generated GraphQL API will allow search by terms in the title.

```graphql
query {
    queryPost(filter: { title: { anyofterms: "GraphQL" }}) {
        ...
    }
}
```

Dgraph also builds search into the fields of each type, so searching is
available at deep levels in a query. For example, if the schema contained these
types

```graphql
type Post {
    ...
    title: String @search(by: [term])
}

type Author {
    name: String @search(by: [hash])
    posts: [Post]
}
```

then Dgraph builds GraphQL search such that a query can, for example, find an
author by name (from the hash search on `name`) and return only their posts that
contain the term "GraphQL".

```graphql
queryAuthor(filter: { name: { eq: "Diggy" } } ) {
    }
}
```

Dgraph can build search types with the ability to search between a range. For
example, with the preceding Post type with the `datePublished` field, a query
can find publish dates within a range.

```graphql
query {
    queryPost(filter: { datePublished: { between: { min: "2020-06-15", max: "2020-06-16" }}}) {
        ...
    }
}
```

Dgraph can also build GraphQL search ability to find match a value from a list.
For example with the preceding Author type with the name field, a query can
return the Authors that match a list

```graphql
queryAuthor(filter: { name: { in: ["Diggy", "Jarvis"] } } ) {
    ...
}
```

There's different search possible for each type as explained below.

### Int, float and dateTime

| argument | constructed filter                                |
| -------- | ------------------------------------------------- |
| none     | `lt`, `le`, `eq`, `in`, `between`, `ge`, and `gt` |

Search for fields of types `Int`, `Float` and `dateTime` is enabled by adding
`@search` to the field with no arguments. For example, if a schema contains:

```graphql
type Post {
    ...
    numLikes: Int @search
}
```

Dgraph generates search into the API for `numLikes` in two ways: a query for
posts and field search on any post list.

A field `queryPost` is added to the `Query` type of the schema.

```graphql
type Query {
    ...
    queryPost(filter: PostFilter, order: PostOrder, first: Int, offset: Int): [Post]
}
```

`PostFilter` will contain less than `lt`, less than or equal to `le`, equal
`eq`, in list `in`, between range `between`, greater than or equal to `ge`, and
greater than `gt` search on `numLikes`. Allowing for example:

```graphql
query {
    queryPost(filter: { numLikes: { gt: 50 }}) {
        ...
    }
}
```

Also, any field with a type of list of posts has search options added to it. For
example, if the input schema also contained:

```graphql
type Author {
    ...
    posts: [Post]
}
```

Dgraph would insert search into `posts`, with

```graphql
type Author {
    ...
    posts(filter: PostFilter, order: PostOrder, first: Int, offset: Int): [Post]
}
```

That allows search within the GraphQL query. For example, to find Diggy's posts
with more than 50 likes.

```graphql
queryAuthor(filter: { name: { eq: "Diggy" } } ) {
    ...
    posts(filter: { numLikes: { gt: 50 }}) {
        title
        text
    }
}
```

### dateTime

| argument                          | constructed filters                               |
| --------------------------------- | ------------------------------------------------- |
| `year`, `month`, `day`, or `hour` | `lt`, `le`, `eq`, `in`, `between`, `ge`, and `gt` |

As well as `@search` with no arguments, `DateTime` also allows specifying how
the search index should be built: by year, month, day or hour. `@search`
defaults to year, but once you understand your data and query patterns, you
might want to changes that like `@search(by: [day])`.

### Boolean fields

| argument | constructed filter |
| -------- | ------------------ |
| none     | `true` and `false` |

Boolean fields can only be tested for `true` or `false`. If
`isPublished: Boolean @search` is in the schema, then the search allows

```graphql
filter: { isPublished: true }
```

and

```graphql
filter: { isPublished: false }
```

### String

Strings allow a wider variety of search options than other types. For strings,
you have the following options as arguments to `@search`.

| argument   | constructed searches                                                  |
| ---------- | --------------------------------------------------------------------- |
| `hash`     | `eq` and `in`                                                         |
| `exact`    | `lt`, `le`, `eq`, `in`, `between`, `ge`, and `gt` (lexicographically) |
| `regexp`   | `regexp` (regular expressions)                                        |
| `term`     | `allofterms` and `anyofterms`                                         |
| `fulltext` | `alloftext` and `anyoftext`                                           |
| `ngram`    | `ngram`                                                               |

* *Schema rule*: `hash` and `exact` can't be used together.

#### String exact and hash search

Exact and hash search has the standard lexicographic meaning.

```graphql
query {
    queryAuthor(filter: { name: { eq: "Diggy" } }) { ... }
}
```

And for exact search

```graphql
query {
    queryAuthor(filter: { name: { gt: "Diggy" } }) { ... }
}
```

to find users with names lexicographically after "Diggy."

#### String regular expression search

Search by regular expression requires bracketing the expression with `/` and
`/`. For example, query for "Diggy" and anyone else with "iggy" in their name:

```graphql
query {
    queryAuthor(filter: { name: { regexp: "/.*iggy.*/" } }) { ... }
}
```

#### String term and fulltext search

If the schema has

```graphql
type Post {
    title: String @search(by: [term])
    text: String @search(by: [fulltext])
    ...
}
```

then

```graphql
query {
    queryPost(filter: { title: { `allofterms: "GraphQL tutorial"` } } ) { ... }
}
```

`anyofterms: "GraphQL tutorial"` would match posts with either "GraphQL" or

example, to find posts that talk about fantastic GraphQL tutorials:

```graphql
query {
    queryPost(filter: { title: { `alloftext: "fantastic GraphQL tutorials"` } } ) { ... }
}
```

#### String ngram search

The `ngram` index tokenizes a string into contiguous sequences of n words, with
support for stop word removal and stemming. N-gram search matches if the indexed
string contains the given sequence of terms.

If the schema has

```graphql
type Post {
    title: String @search(by: [ngram])
    ...
}
```

then

```graphql
query {
    queryPost(filter: { title: { ngram: "quick brown fox" } } ) { ... }
}
```

will match all posts that contain the contiguous sequence "quick brown fox" in
the title.

#### Strings with multiple searches

It is possible to add multiple string indexes to a field. For example to search
for authors by `eq` and regular expressions, add both options to the type
definition, as follows.

```graphql
type Author {
    ...
    name: String! @search(by: [hash, regexp])
}
```

### enums

| argument | constructed searches                                                  |
| -------- | --------------------------------------------------------------------- |
| none     | `eq` and `in`                                                         |
| `hash`   | `eq` and `in`                                                         |
| `exact`  | `lt`, `le`, `eq`, `in`, `between`, `ge`, and `gt` (lexicographically) |
| `regexp` | `regexp` (regular expressions)                                        |

enum fields are serialized in Dgraph as strings. `@search` with no arguments is
the same as `@search(by: [hash])` and provides `eq` and `in` searches. Also
available for enums are `exact` and `regexp`. For hash and exact search on
enums, the literal enum value, without quotes `"..."`, is used, for regexp,
strings are required. For example:

```graphql
enum Tag {
    GraphQL
    Database
    Question
    ...
}

type Post {
    ...
    tags: [Tag!]! @search
}
```

would allow

```graphql
query {
    queryPost(filter: { tags: { eq: GraphQL } } ) { ... }
}
```

Which would find any post with the `GraphQL` tag.

While `@search(by: [exact, regexp]` would also admit `lt` etc. and

```graphql
query {
    queryPost(filter: { tags: { regexp: "/.*aph.*/" } } ) { ... }
}
```

which is helpful for example if the enums are something like product codes where
regular expressions can match a number of values.

### Geolocation

There are 3 Geolocation types: `Point`, `Polygon` and `MultiPolygon`. All of
them are searchable.

The following table lists the generated filters for each type when you include
`@search` on the corresponding field:

| type           | constructed searches                       |
| -------------- | ------------------------------------------ |
| `Point`        | `near`, `within`                           |
| `Polygon`      | `near`, `within`, `contains`, `intersects` |
| `MultiPolygon` | `near`, `within`, `contains`, `intersects` |

#### Example

Take for example a `Hotel` type that has a `location` and an `area`:

```graphql
type Hotel {
  id: ID!
  name: String!
  location: Point @search
  area: Polygon @search
}
```

#### Near

The `near` filter matches all entities where the location given by a field is
within a distance `meters` from a coordinate.

```graphql
queryHotel(filter: {
    location: {
        near: {
            coordinate: {
                latitude: 37.771935,
                longitude: -122.469829
            },
            distance: 1000
        }
    }
}) {
  name
}
```

#### Within

The `within` filter matches all entities where the location given by a field is
within a defined `polygon`.

```graphql
queryHotel(filter: {
    location: {
        within: {
            polygon: {
                coordinates: [{
                    points: [{
                        latitude: 11.11,
                        longitude: 22.22
                    }, {
                        latitude: 15.15,
                        longitude: 16.16
                    }, {
                        latitude: 20.20,
                        longitude: 21.21
                    }, {
                        latitude: 11.11,
                        longitude: 22.22
                    }]
                }],
            }
        }
    }
}) {
  name
}
```

#### Contains

The `contains` filter matches all entities where the `Polygon` or `MultiPolygon`
field contains another given `point` or `polygon`.

<Tip>
  Only one `point` or `polygon` can be taken inside the `ContainsFilter` at a
  time.
</Tip>

A `contains` example using `point`:

```graphql
queryHotel(filter: {
    area: {
        contains: {
            point: {
              latitude: 0.5,
              longitude: 2.5
            }
        }
    }
}) {
  name
}
```

A `contains` example using `polygon`:

```graphql
 queryHotel(filter: {
    area: {
        contains: {
            polygon: {
                coordinates: [{
                  points:[{
                    latitude: 37.771935,
                    longitude: -122.469829
                  }]
                }],
            }
        }
    }
}) {
  name
}
```

#### Intersects

The `intersects` filter matches all entities where the `Polygon` or
`MultiPolygon` field intersects another given `polygon` or `multiPolygon`.

<Tip>
  Only one `polygon` or `multiPolygon` can be given inside the
  `IntersectsFilter` at a time.
</Tip>

```graphql
  queryHotel(filter: {
    area: {
      intersects: {
        multiPolygon: {
          polygons: [{
            coordinates: [{
              points: [{
                latitude: 11.11,
                longitude: 22.22
              }, {
                latitude: 15.15,
                longitude: 16.16
              }, {
                latitude: 20.20,
                longitude: 21.21
              }, {
                latitude: 11.11,
                longitude: 22.22
              }]
            }, {
              points: [{
                latitude: 11.18,
                longitude: 22.28
              }, {
                latitude: 15.18,
                longitude: 16.18
              }, {
                latitude: 20.28,
                longitude: 21.28
              }, {
                latitude: 11.18,
                longitude: 22.28
              }]
            }]
          }, {
            coordinates: [{
              points: [{
                latitude: 91.11,
                longitude: 92.22
              }, {
                latitude: 15.15,
                longitude: 16.16
              }, {
                latitude: 20.20,
                longitude: 21.21
              }, {
                latitude: 91.11,
                longitude: 92.22
              }]
            }, {
              points: [{
                latitude: 11.18,
                longitude: 22.28
              }, {
                latitude: 15.18,
                longitude: 16.18
              }, {
                latitude: 20.28,
                longitude: 21.28
              }, {
                latitude: 11.18,
                longitude: 22.28
              }]
            }]
          }]
        }
      }
    }
  }) {
    name
  }
```

### Union

Unions can be queried only as a field of a type. Union queries can't be ordered,
but you can filter and paginate them.

<Note>
  Union queries don't support the `order` argument. The results will be ordered
  by the UID of each node in ascending order.
</Note>

For example, the following schema will enable to query the `members` union field
in the `Home` type with filters and pagination.

```graphql
union HomeMember = Dog | Parrot | Human

type Home {
  id: ID!
  address: String

  members(filter: HomeMemberFilter, first: Int, offset: Int): [HomeMember]
}

# Not specifying a field in the filter input will be considered as a null value for that field.
input HomeMemberFilter {
  # `homeMemberTypes` is used to specify which types to report back.
  homeMemberTypes: [HomeMemberType]

  # specifying a null value for this field means query all dogs
  dogFilter: DogFilter

  # specifying a null value for this field means query all parrots
  parrotFilter: ParrotFilter
  # note that there is no HumanFilter because the Human type wasn't filterable
}

enum HomeMemberType {
  dog
  parrot
  human
}

input DogFilter {
  id: [ID!]
  category: Category_hash
  breed: StringTermFilter
  and: DogFilter
  or: DogFilter
  not: DogFilter
}

input ParrotFilter {
  id: [ID!]
  category: Category_hash
  and: ParrotFilter
  or: ParrotFilter
  not: ParrotFilter
}
```

<Tip>
  Not specifying any filter at all or specifying any of the `null` values for a
  filter will query all members.
</Tip>

The same example, but this time with filter and pagination arguments:

```graphql
query {
  queryHome {
    address
    members(
      filter: {
        homeMemberTypes: [dog, parrot] # means we don't want to query humans
        dogFilter: {
          # means in Dogs, we only want to query "German Shepherd" breed
          breed: { allofterms: "German Shepherd" }
        }
        # not specifying any filter for parrots means we want to query all parrots
      }
      first: 5
      offset: 10
    ) {
      ... on Animal {
        category
      }
      ... on Dog {
        breed
      }
      ... on Parrot {
        repeatsWords
      }
      ... on HomeMember {
        name
      }
    }
  }
}
```

### Vector embedding

The `@search` directive is used in conjunction with `@embedding` directive to
define the HNSW index on vector embeddings. These vector embeddings are obtained
from external Machine Learning models.

```graphql
type User {
  userID: ID!
  name: String!
  name_v: [Float!]
    @embedding
    @search(by: ["hnsw(metric: euclidean, exponent: 4)"])
}
```

In this schema, the field `name_v` is an embedding on which the HNSW algorithm
is used to create a vector search index.

The metric used to compute the distance between vectors (in this example) is
Euclidean distance. Other possible metrics are `cosine` and `dotproduct`.

The directive, `@embedding`, designates one or more fields as vector embeddings.

The `exponent` value is used to set reasonable defaults for HNSW internal tuning
parameters. It is an integer representing an approximate number for the vectors
expected in the index, in terms of power of 10. Default is “4” (10^4 vectors).