Ask a Question

Search and Filtering

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

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

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

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

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

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

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

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

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”.

queryAuthor(filter: { name: { eq: "Diggy" } } ) {
    posts(filter: { title: { anyofterms: "GraphQL" }}) {
        title
    }
}

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

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 above Author type with the name field, a query can return the Authors that match a list

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:

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.

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:

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:

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

Dgraph would insert search into posts, with

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.

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

argument constructed filter
none true and false

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

filter: { isPublished: true }

and

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
  • Schema rule: hash and exact can’t be used together.

Exact and hash search has the standard lexicographic meaning.

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

And for exact search

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

to find users with names lexicographically after “Diggy”.

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

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

If the schema has

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

then

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

will match all posts with both “GraphQL and “tutorial” in the title, while anyofterms: "GraphQL tutorial" would match posts with either “GraphQL” or “tutorial”.

fulltext search is Google-stye text search with stop words, stemming. etc. So alloftext: "run woman" would match “run” as well as “running”, etc. For example, to find posts that talk about fantastic GraphQL tutorials:

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

Strings with multiple searches

It’s 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.

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)

Enums 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:

enum Tag {
    GraphQL
    Database
    Question
    ...
}

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

would allow

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

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:

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.

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.

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.

A contains example using point:

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

A contains example using polygon:

 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.
  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 do not support the order argument. The results will be ordered by the uid of each node in ascending order.

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

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.

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

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 @embeding directive to define the HNSW index on vector embeddings. These vector embeddings are obtained from external Machine Learning models.

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).