export const meta = { title: `Search and result aggregations`, description: `Add authorization rules to your GraphQL schema to control access to your data.`, }; Add the `@searchable` directive to an `@model` type to enable OpenSearch-based data search and result aggregations. This gives you the ability to: - search for data using advanced filters, such as substring matching, wildcards, regex, `and`/`or`/`not` conditions - get aggregation values, such as sum, average, min, max, terms - retrieve total search result count - sort the search results across one or multiple fields ```graphql type Student @model @searchable { name: String dateOfBirth: AWSDate email: AWSEmail examsCompleted: Int } ``` > Once the `@searchable` directive is added, all new records added to the model are streamed to OpenSearch. To backfill existing data, see [Backfill OpenSearch index from DynamoDB table](/cli/graphql/troubleshooting#backfill-opensearch-index-from-dynamodb-table). ## Search and filter data Every model with a `@searchable` directive attached generates a new "search" GraphQL query to search and filter for records. The example above provides you the ability to search for "Student" records using a "searchStudents" query. The `filter` parameter allows you to filter for records based on their field values. ```graphql query SearchStudentsByEmail { searchStudents(filter: { name: { eq: "Rene Brandel" }}) { items { id name email } } } ``` In the example above, the search result consists of students with the name "Rene Brandel" ### Supported search operations |Field type|Supported search operations| |-|-| |String|ne, eq, match, matchPhrase, matchPhrasePrefix, multiMatch, exists, wildcard, regexp| |Int|ne, gt, lt, gte, lte, eq, range| |Float|ne, gt, lt, gte, lte, eq, range| |Boolean|eq, ne| |Enum|ne, eq, match, matchPhrase, matchPhrasePrefix, multiMatch, exists, wildcard, regexp| ### Nested search conditions (and, or, not) Use the filter parameter to pass a nested `and`/`or`/`not` condition. ```graphql query MyQuery { searchStudents(filter: { name: {wildcard: "*Brandel"} or: [ { dateOfBirth: { lt: "2000-01-01" } }, { email: { exists: true } } ] }) { items { id name email dateOfBirth } } } ``` By default, every operation in the filter properties is `and`ed. Use the `or` or `not` properties in the search query's `filter` parameter to override this behavior. The query above returns a "Student" if: - their name ends with "Brandel" - `and` - their date of birth is earlier than 2000-01-01 - `or` - their email exists. ## Sort search results Use the `sort` parameter to sort your search results by a field in ascending or descending order. The `field` argument accepts any field available on the model. The `direction` accepts either `asc` or `desc`. ```graphql query SearchAndSort { searchStudents( filter: { name: { wildcard: "*Brandel" } }, sort: { direction: desc, field: name } ) { items { name id } } } ``` In the example above, the search result is sorted based on their `name` in a `desc`ending order. ### Sort search result over multiple fields To sort over multiple fields, provide array of sort conditions. When sorting over multiple fields, the sort conditions are applied in the `sort` array's order. ```graphql query SearchAndSort { searchStudents( filter: {name: {wildcard: "*Brandel"}}, sort: [ {field: name, direction: desc}, # Sort condition #1 {field: dateOfBirth, direction: asc} # Sort condition #2 ] ) { items { id name dateOfBirth } } } ``` In the example above, the search result is first sorted by `name` in a `desc`ending order and then by `dateOfBirth` in an `asc`ending order. ## Paginate over search results By default, the search result page size is 100. To customize the page size modify the `limit` parameter. Query for the `nextToken` and use it in your subsequent pagination requests: ``` query MyQuery { searchTodos(nextToken: "") { # Pass in your nextToken in query items { description id name createdAt } nextToken # Next token to paginate on } } ``` ## Total count of search results Add the `total` field in your query response to get the total count of search result hits. ```graphql query MyQuery { searchStudents(filter: {name: {wildcard: "*Brandel"}}) { items { id } total # Specify to get total counts } } ``` In the example above, the response's `total` field contains the total search result count for "Students" whose name ends with "Brandel". Note: `total` is calculated based on all records, irrespective of pagination configurations. ## Aggregate values for search result (minimum, maximum, average, sum, terms) Use the `aggregates` parameter to get aggregate values such as "minimum", "maximum", "average", and "sum" returned in the `aggregateItems` field. Note: `aggregates` are calculated based on all records, irrespective of pagination configurations. Provide the `min` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. ```graphql query MyQuery { searchStudents( aggregates: { type: min, # Specifies that you want the "min" value field: examsCompleted, # Specifies the field for the aggregate value name: "minimumExams" # provides a name to reference in the response field } filter: { name: {wildcard: "Rene*"}}) { aggregateItems { name result { ... on SearchableAggregateScalarResult { value } } } } } ``` In the example above, the response includes the minimum value of "examsCompleted" for all Students whose name starts with "Rene". ```graphql { "data": { "searchStudents": { "aggregateItems": [{ "name": "minimumExams", "result": { "value": 7 } }] } } } ``` Provide the `max` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. ```graphql query MyQuery { searchStudents( aggregates: { type: max, # Specifies that you want the "max" value field: examsCompleted, # Specifies the field for the aggregate value name: "maximumExams" # provides a name to reference in the response field } filter: { name: {wildcard: "Rene*"}}) { aggregateItems { name result { ... on SearchableAggregateScalarResult { value } } } } } ``` In the example above, the response includes the maximum value of "examsCompleted" for all Students whose name starts with "Rene". ```graphql { "data": { "searchStudents": { "aggregateItems": [{ "name": "maximumExams", "result": { "value": 28 } }] } } } ``` Provide the `avg` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. ```graphql query MyQuery { searchStudents( aggregates: { type: avg, # Specifies that you want the "avg" value field: examsCompleted, # Specifies the field for the aggregate value name: "averageExams" # provides a name to reference in the response field } filter: { name: {wildcard: "Rene*"}}) { aggregateItems { name result { ... on SearchableAggregateScalarResult { value } } } } } ``` In the example above, the response includes the average value of "examsCompleted" for all Students whose name starts with "Rene". ```graphql { "data": { "searchStudents": { "aggregateItems": [{ "name": "averageExams", "result": { "value": 17.3 } }] } } } ``` Provide the `sum` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. ```graphql query MyQuery { searchStudents( aggregates: { type: sum, # Specifies that you want the "sum" value field: examsCompleted, # Specifies the field for the aggregate value name: "examsSum" # provides a name to reference in the response field } filter: { name: {wildcard: "Rene*"}}) { aggregateItems { name result { ... on SearchableAggregateScalarResult { value } } } } } ``` In the example above, the response includes the sum of all "examsCompleted" values for all Students whose name starts with "Rene". ```graphql { "data": { "searchStudents": { "aggregateItems": [{ "name": "examsSum", "result": { "value": 392 } }] } } } ``` Provide the `terms` value as the `aggregate` `type` and specify the `aggregateItems` in the response field. ```graphql query MyQuery { searchTodos(aggregates: { field: description, type: terms, name: "descriptionTerms" }) { aggregateItems { result { ... on SearchableAggregateBucketResult { __typename buckets { doc_count key } } } name } } } ``` In the example above, the response includes the terms for the description and their count: ```graphql { "data": { "searchTodos": { "aggregateItems": [ { "result": { "__typename": "SearchableAggregateBucketResult", "buckets": [{ "doc_count": 2, "key": "Shopping list" }, { "doc_count": 1, "key": "Me next todo" }] }, "name": "descriptionTerms" } ] } } } ``` ## Set up OpenSearch for production environments By default, Amplify CLI will configure a t2.small instance type. This is great for getting started and prototyping but not recommended to be used in the production environment per the [OpenSearch best practices](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/bp.html). To configure the OpenSearch instance type per environment: 1. Run `amplify env add` to create a new environment (e.g. "prod") 2. Edit the `amplify/team-provider-info.json` file and set `OpenSearchInstanceType` to the instance type that works for your application ```json { "dev": { "categories": { "api": { "" : { "OpenSearchInstanceType": "t2.small.elasticsearch" } } } }, "prod": { "categories": { "api": { "" : { "OpenSearchInstanceType": "t2.medium.elasticsearch" } } } } } ``` 3. Deploy your changes with `amplify push` Learn more about Amazon OpenSearch Service instance types [here](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/supported-instance-types.html). ## How it works The `@searchable` directive streams the data of an @model type to Amazon OpenSearch Service and configures search resolvers to query against OpenSearch. Type definition of the `@searchable` directive: ```graphql # Streams data from DynamoDB to OpenSearch and exposes search capabilities. directive @searchable(queries: SearchableQueryMap) on OBJECT input SearchableQueryMap { search: String } ```