Search Documents
Tigris offers a realtime search for documents in a collection. Searching is by default enabled for a collection, there is no setup. This guide section will walk through how to use Tigris search in different scenarios.
Example collection​
Let's first have the collection.
const catalog = db.getCollection<Catalog>("catalog");
Assuming an e-commerce website that has the above collection catalog and has 6 products(documents) in it.
id | name | price | brand | labels | popularity | reviews |
---|---|---|---|---|---|---|
1 | fiona handbag | 99.9 | michael kors | purses | 8 | {"author": "alice", "rating": 7} |
2 | tote bag | 49 | coach | handbags | 9 | {"author": "olivia", "rating": 8.3} |
3 | sling bag | 75 | coach | purses | 9 | {"author": "alice", "rating": 9.2} |
4 | sneakers shoes | 40 | adidas | shoes | 10 | {"author": "olivia", "rating": 9} |
5 | running shoes | 89 | nike | shoes | 10 | {"author": "olivia", "rating": 8.5} |
6 | running shorts | 35 | adidas | clothing | 7 | {"author": "olivia", "rating": 7.5} |
Searching for documents​
Search consists of executing a query against one or more text fields. Let's perform a simple search query to lookup any items matching "running".
const query: SearchQuery<Catalog> = {
q: "running",
};
const results = catalog.search(query);
for await (const result of results) {
console.log(result);
}
Search is case insensitive i.e. a search for term "ruNninG"
would match all of ["Running", "running", "RUnnIng", "RUNNING"]
etc.
Phrase search​
By default, search is performed over individual terms in the text. For example, search for a
query string adventure park
in a dataset would yield following results:
- "California's kids adventure park and Safari"
- "Adventure island and water park"
- "Long Island water park and adventure activities"
- "Six flags kids recreation and adventure park"
- "Hollywood adventure park and studios"
- "Seaworld adventure and theme park"
The search phrase can be escaped in query for exact match. In the above example, querying for exact
phrase \"adventure park\"
would return:
- "California's kids adventure park and Safari"
- "Six flags kids recreation and adventure park"
- "The Great America adventure park and Zoo"
Phrases can still be combined with keywords for richer text search. Continuing above example,
the query string kids \"adventure park\"
would result in:
- "California's kids adventure park and Safari"
- "Six flags kids recreation and adventure park"
const query: SearchQuery<Catalog> = {
q: "\"adventure park\"",
};
const request: SearchRequest<Catalog> = {
q: "kids \"adventure park\"",
};
Match all search query​
When query string isn't specified or an empty string (""
), a match all query is performed.
It returns all searchable documents, modified by any filters or search parameters used.
const query: SearchQuery<Catalog> = { };
Returning all documents is typically useful when used in conjunction with filter, or when performing a faceted search across the collection.
Project search query against specific fields​
We can optionally project the search query against selected fields. Continuing previous example of
searching for "running", we may not want to search in reviews
field and avoid any
unwanted results.
const query: SearchQuery<Catalog> = {
q: "running",
searchFields: ["name", "labels"]
};
Refine the search results using filters​
Let's adjust the query to only return items in price
range of [40, 90). We can use
filters in search to further refine the results.
const query: SearchQuery<Catalog> = {
q: "running",
searchFields: ["name", "labels"],
filter: {
op: LogicalOperator.AND,
selectorFilters: [
{
op: SelectorFilterOperator.GTE,
fields: {
price: 40
}
},
{
op: SelectorFilterOperator.LT,
fields: {
price: 90
}
}
]
}
};
Exact text match​
Filters can be used to match against one or more field values in a collection. For example, to
fetch all items from brand
"adidas".
const query: SearchQuery<Catalog> = {
filter: { brand: "adidas" }
};
Faceted search​
We can additionally retrieve the number of items a particular brand
has and unique labels
,
that match our search query.
const query: SearchQuery<Catalog> = {
q: "running",
searchFields: ["name", "labels"],
filter: {
op: LogicalOperator.AND,
selectorFilters: [
{
op: SelectorFilterOperator.GTE,
fields: {
price: 40
}
},
{
op: SelectorFilterOperator.LT,
fields: {
price: 90
}
}
]
},
facets: ["brand", "labels"],
};
Facets are a specific use-case of filters, and can only be used for filterable attributes.
Common application for faceted search is to build UX with quick filters, that users can use to narrow search results in real-time. Faceted search interface presents intuitive content navigation to the end user.
Sorting the search results​
Tigris lets you specify an order to sort the search results. We can specify a ranking order in our search query to have results sorted with more popular items appearing first.
const query: SearchQuery<Catalog> = {
q: "running",
searchFields: ["name", "labels"],
sort: [
{
field: "popularity",
order: "$desc",
},
],
};
Many documents may have the same popularity
score, we can specify additional user-defined sortable
field to break the tie.
const query: SearchQuery<Catalog> = {
q: "running",
searchFields: ["name", "labels"],
sort: [
{
field: "popularity",
order: "$desc"
},
{
field: "reviews.rating",
order: "$desc"
}
]
};
The results will be first sorted by value of popularity
field, reviews.rating
will be used to
decide ordering if two matching documents have same popularity
.
Documents can only be sorted by integer
, number
and date-time
type of collection fields.
Specifying document fields to retrieve​
Search query can be programmed to return only specific fields in a document in search results. We
may only need to retrieve product name
, brand
and price
for our interface.
const query: SearchQuery<Catalog> = {
q: "running",
includeFields: ["name", "price", "brand"]
};
On the contrary, exclusion of fields is useful to exclude/hide potentially sensitive fields or
internal metadata from the document. To include all fields except id
and reviews
from documents
in search results.
const query: SearchQuery<Catalog> = {
q: "running",
excludeFields: ["id", "reviews"]
};
Field selection does not impact searching, filtering and faceting capabilities for that field. For
example, if reviews
field is not included in documents in search results, it could still be used
for text querying, filtering and/or faceting; just that matched documents won't include reviews
field.
Case Insensitive Search Result Filtering​
Search is case-insensitive but the filtering to restrict the search result is case-sensitive by default. Tigris supports
Collation which allows you to specify string comparison rules for filtering on text fields. Set the case to ci
in the
collation object to make it case-insensitive. The following example is showing when you are searching for text "running"
and you need to filter by brand
field, but you don't care about the case.
const query: SearchQuery<Catalog> = {
q: "running",
filter: {
brand: "Adidas",
},
options: { collation: Case.CaseInsensitive },
};
const results = catalog.search(query);
for await (const result of results) {
console.log(result);
}
Paginating through search results​
Using page numbers​
To retrieve a page of results, you can simply use search(query, page)
method with page number and
page size. Following query fetches the first page of results with page size set as 10
const query: SearchQuery<Catalog> = {
q: "running",
hitsPerPage: 10, // Optional. Defaults to 20
};
const result: SearchResult<Catalog> = await catalog.search(query, 1);
console.log(result);
Output
{
"hits": [
{
"document": {
"brand": "adidas",
"price": 35,
"review": {
"author": "olivia",
"rating": 7.5
},
"popularity": 7,
"id": "6",
"labels": "clothing",
"name": "running shorts"
},
"meta": {
"createdAt": "2023-03-13T21:42:35.000Z",
"textMatch": {
"fields": [
"name"
],
"score": "578730123365187697"
}
}
},
{
"document": {
"id": "5",
"brand": "nike",
"name": "running shoes",
"price": 89,
"labels": "shoes",
"popularity": 10,
"review": {
"rating": 8.5,
"author": "olivia"
}
},
"meta": {
"createdAt": "2023-03-13T21:42:35.000Z",
"textMatch": {
"fields": [
"name"
],
"score": "578730123365187697"
}
}
}
],
"facets": {},
"meta": {
"found": 23,
"totalPages": 3,
"page": {
"current": 1,
"size": 10
},
"matchedFields": [
"name"
]
}
}
The hitsPerPage
parameter controls the number of documents to include in a result page. The returned
array of documents is accessible under hits
key along with some search metadata.
console.log(result.hits);
Output
[
{
"document": {
"labels": "clothing",
"popularity": 7,
"id": "6",
"name": "running shorts",
"review": {
"rating": 7.5,
"author": "olivia"
},
"price": 35,
"brand": "adidas"
},
"meta": {
"createdAt": "2023-03-13T21:42:07.000Z",
"textMatch": {
"fields": [
"name"
],
"score": "578730123365187697"
}
}
},
{
"document": {
"name": "running shoes",
"id": "5",
"price": 89,
"popularity": 10,
"review": {
"author": "olivia",
"rating": 8.5
},
"brand": "nike",
"labels": "shoes"
},
"meta": {
"createdAt": "2023-03-13T21:42:07.000Z",
"textMatch": {
"fields": [
"name"
],
"score": "578730123365187697"
}
}
}
]
Additionally, search result contains meta
object having current page and total pages along with
other information.
console.log(result.meta);
Output
{
"found": 23,
"totalPages": 3,
"page": {
"current": 1,
"size": 10
},
"matchedFields": [
"name"
]
}
Infinite scrolling​
Infinite scrolling also loads data in pages, it is just that the UX is more fluid.
Instead of using page number, an Iterator object can be obtained from search()
method call and processed iteratively.
const query: SearchQuery<Catalog> = {
q: "running",
hitsPerPage: 10, // Optional. Defaults to 20
};
const resultIterable: SearchIterator<Catalog> = await catalog.search(query);
for await (const result of resultIterable) {
console.log(result);
}
Output
{
"hits": [
{
"document": {
"brand": "adidas",
"price": 35,
"review": {
"author": "olivia",
"rating": 7.5
},
"popularity": 7,
"id": "6",
"labels": "clothing",
"name": "running shorts"
},
"meta": {
"createdAt": "2023-03-13T21:42:35.000Z",
"textMatch": {
"fields": [
"name"
],
"score": "578730123365187697"
}
}
},
{
"document": {
"id": "5",
"brand": "nike",
"name": "running shoes",
"price": 89,
"labels": "shoes",
"popularity": 10,
"review": {
"rating": 8.5,
"author": "olivia"
}
},
"meta": {
"createdAt": "2023-03-13T21:42:35.000Z",
"textMatch": {
"fields": [
"name"
],
"score": "578730123365187697"
}
}
}
],
"facets": {},
"meta": {
"found": 23,
"totalPages": 3,
"page": {
"current": 1,
"size": 10
},
"matchedFields": [
"name"
]
}
}
As you can see, the iterator returns the same SearchResult
object as in previous section
with pagination metadata.