Full-Text Search
Once you have your SDK for your preferred language installed, you can start performing full-text, vector, and hybrid search queries on Orama Cloud.
The client exposes a simple search
method that can be used to query the index:
import { OramaClient } from "@oramacloud/client";
const client = new OramaClient({ endpoint: '<Your Orama Cloud Endpoint>', api_key: '<Your Orama Cloud API Key>',});
const results = await client.search({ term: "red shoes", mode: "fulltext", // optional, default is "fulltext" but can also be "vector" or "hybrid" where: { price: { gt: 99.99, }, },});
import OramaCloudClient
struct MyDoc: Codable { let title: String let description: String}
let clientParams = OramaClientParams(endpoint: "<Your Orama Cloud Endpoint>", apiKey: "<Your Orama Cloud API Key>")let client = OramaClient(params: clientParams)
let searchParams = ClientSearchParams.builder( term: "red shoes", mode: .fulltext // optional, default is .fulltext but can also be .vector or .hybrid ) .limit(10) // optional .offset(0) // optional .returning(["title", "description"]) // optional .build()
let searchResults: SearchResults<MyDoc> = try await client.search(query: searchParams)
import com.orama.client.OramaClientimport com.orama.model.search.*import kotlinx.serialization.Serializable
// Keep in mind that search is a suspended function,// so you need to call it from a coroutine ;)
@Serializabledata class MyDoc ( val title: String, val category: String, val path: String, val content: String, val section: String)
val searchParams = SearchParams.builder( term = "red shoes", mode = Mode.FULLTEXT ) .where(listOf( Condition("price", ConditionType.GreaterThan(99.99)) )) .build()
val results = client.search(searchParams, MyDoc.serializer())
print("coming soon")
use OramaCloud\Client;use OramaCloud\Client\Query;use OramaCloud\Client\QueryParams\WhereOperator;use OramaCloud\Client\QueryParams\SortByOrder;
$client = new Client([ 'endpoint' => '<Your Orama Cloud Endpoint>', 'api_key' => '<Your Orama Cloud API Key>']);
$query = (new Query()) ->term('red shoes') ->mode('fulltext') // 'fulltext' is optional, but can also be "vector" or "hybrid" ->where('price', WhereOperator::GT, 99.99);
$results = $client->search($query);
Sorting
To perform sorting, Orama Cloud uses the properties defined in the schema
to know on which properties you want to perform sorting.
Considering the following schema:
{ "title": "string", "year": "number", "inPromotion": "boolean", "meta": { "tag": "string", "rating": "number", "favorite": "boolean" }}
const results = await client.search({ term: "prestige", sortBy: { property: "year", order: "desc" // optional, default is "asc" },});
let searchQuery = ClientSearchParams .builder(term: "prestige", mode: .fulltext) .sortBy([ ClientSearchParams.SortByDirective( property: "year", order: .asc ) ]) .build()
let results: SearchResults<MyDoc> = try await client.search(query: searchQuery)
val searchParams = SearchParams.builder( term = "prestige" ) .sortBy([ sortByDirective("year", SortByOrder.ASC) ]) .build()
val results = client.search(searchParams, MyDoc.serializer())
print("coming soon")
use OramaCloud\Client;use OramaCloud\Client\Query;use OramaCloud\Client\QueryParams\SortByOrder;
$query = (new Query()) ->term('prestige') ->sortBy('year' SortByOrder::DESC); // optional, default is "ASC"
$results = $client->search($query);
With Orama Cloud, you can set up to three sorting properties. This means that you can sort by up to three properties at the same time.
So, considering the following schema:
{ "title": "string", "year": "number", "inPromotion": "boolean", "meta": { "tag": "string", "rating": "number", "favorite": "boolean" }}
You can sort by year
and meta.rating
at the same time:
const results = await client.search({ term: "prestige", sortBy: [ { property: "year", order: "desc" }, { property: "meta.rating", order: "desc" } ]});
let searchQuery = ClientSearchParams .builder(term: "prestige", mode: .fulltext) .sortBy([ ClientSearchParams.SortByDirective( property: "year", order: .asc ), ClientSearchParams.SortByDirective( property: "meta.rating", order: .desc ), ]) .build()
let results: SearchResults<MyDoc> = try await client.search(query: searchQuery)
val searchParams = SearchParams.builder( term = "prestige" ) .sortBy([ sortByDirective("year", Order.ASC), sortByDirective("meta.rating", Order.DESC) ]) .build()
val results = client.search(searchParams, MyDoc.serializer())
print("coming soon")
use OramaCloud\Client;use OramaCloud\Client\Query;use OramaCloud\Client\QueryParams\SortByOrder;
$query = (new Query()) ->term('prestige') ->sortBy('year' SortByOrder::DESC) ->sortBy('meta.rating' SortByOrder::DESC);
$results = $client->search($query);
In that case, “year” will be the primary sorting property, and “meta.rating” will be the secondary sorting property. In case of a tie, the secondary sorting property will be used to break the tie.
Facets
Facets are a powerful tool for filtering and narrowing down search results on the Orama search engine.
With the Orama Faceted Search API, users can filter their search results by various criteria, such as category, price range, or other attributes, making it easier to find the information they need. Whether you’re building a website, mobile app, or any other application, the Orama Faceted Search API is the perfect solution for adding faceted search functionality to your project.
Given the following Orama schema:
{ "title": "string", "description": "string", "categories": { "primary": "string", "secondary": "string", }, "rating": "number", "isFavorite": "boolean",}
Orama will be able to generate facets at search-time based on the schema. To do so, we need to specify the facets
property in the search
configuration:
const results = await client.search({ term: "Movie about cars and racing", mode: "hybrid", properties: ["description"], facets: { "categories.primary": { limit: 3, order: "DESC", }, "categories.secondary": { limit: 2, order: "DESC", }, rating: { ranges: [ { from: 0, to: 3 }, { from: 3, to: 7 }, { from: 7, to: 10 }, ], }, isFavorite: { true: true, false: true, }, },});
import OramaCloudClient
struct MyDoc: Codable { let title: String let description: String}
let clientParams = OramaClientParams(endpoint: "<Your Orama Cloud Endpoint>", apiKey: "<Your Orama Cloud API Key>")let client = OramaClient(params: clientParams)
let searchParams = ClientSearchParams.builder( term: "Movie about cars and racing", mode: .hybrid ) .properties(["description"]) // optional .facets([ "category.primary": .string(limit: 3, order: .desc), "category.secondary": .string(limit: 2, order: .desc), "rating": .number(ranges: [ ClientSearchParams.Facet.NumberRange(from: 0, to: 3), ClientSearchParams.Facet.NumberRange(from: 3, to: 7), ClientSearchParams.Facet.NumberRange(from: 7, to: 10), ]), "isFavorite": .boolean(isTrue: true, isFalse: true), ]) .build()
let searchResults: SearchResults<MyDoc> = try await client.search(query: searchParams)
val searchParams = SearchParams.builder( term = "Movie about cars and racing", mode = Mode.HYBRID, ) .properties(listOf("description")) .facets( mapOf( "category.primary" to Facet.StringFacet(limit = 3, order = Order.DESC), "category.secondary" to Facet.StringFacet(limit = 2, order = Order.DESC), "rating" to Facet.NumberFacet( ranges = listOf( Facet.NumberRange(from = 0, to = 3), Facet.NumberRange(from = 3, to = 7), Facet.NumberRange(from = 7, to = 10) ) ), "isFavorite" to Facet.BooleanFacet(isTrue = true, isFalse = true) ) ) .build()
val results = client.search(searchParams, MyDoc.serializer())
print("coming soon")
// coming soon
This will generate the following result:
{ "elapsed": ..., "count": ..., "hits": { ... }, "facets": { "categories.first": { "count": 14, "values": { "Action": 4, "Adventure": 3, "Comedy": 2, } }, "categories.second": { "count": 14, "values": { "Cars": 4, "Racing": 3, } }, "rating": { "count": 3, "values": { "0-3": 5, "3-7": 15, "7-10": 80, } }, "isFavorite": { "count": 2, "values": { "true": 5, "false": 95, } }, }}
As you may have noticed, the facets
property is an object
that contains different configurations depending on the property type specified in the schema.
String facets
If a property is specified as string
in the schema, the facet will accept the following
configuration:
Property | Type | Default | Description |
---|---|---|---|
order | string | DESC | Order of the values. Can be either ASC or DESC . |
limit | number | 10 | Maximum number of values to return. |
offset | number | 0 | Number of values to skip. |
In the search result, string
facets will be returned as an object
with the following properties:
{ "count": 14, // Total number of values, now limited to 3 (size) "values": { "Action": 4, // Number of documents that have this value "Adventure": 3, // Number of documents that have this value "Comedy": 2, // Number of documents that have this value }}
Number facets
If a property is specified as number
in the schema, the facet will accept the following
configuration:
Property | Type | Default | Description |
---|---|---|---|
ranges | array | [] | Array of ranges to consider. |
Each range is an object
with the following properties:
Property | Type | Description |
---|---|---|
from | number | Minimum value of the range. |
to | number | Maximum value of the range. |
In the search result, number
facets will be returned as an object
with the following properties:
{ "count": 3, // Total number of ranges "values": { "0-3": 5, // Number of documents that have a value between 0 and 3 (inclusive) "3-7": 15, // Number of documents that have a value between 3 and 7 (inclusive) "7-10": 80, // Number of documents that have a value between 7 and 10 (inclusive) }}
Please note that the from
and to
values are inclusive. Note also that the order of the ranges
is guaranteed as specified in the configuration.
Boolean facets
If a property is specified as boolean
in the schema, the facet will accept the following
configuration:
Property | Type | Default | Description |
---|---|---|---|
true | boolean | true | Whether to consider true values. |
false | boolean | true | Whether to consider false values. |
In the search result, boolean
facets will be returned as an object
with the following properties:
{ "count": 2, // Total number of values "values": { "true": 5, // Number of documents that have a `true` value "false": 95, // Number of documents that have a `false` value }}
Enum facets
If a property is specified as enum
in the schema, no configuration is required.
In the search result, enum
facets will be returned as an object
with the following properties:
{ "count": 9, // Total number of values "values": { "Action": 4, // Number of documents that have this value "Adventure": 3, // Number of documents that have this value "Comedy": 2, // Number of documents that have this value }}
Filters
You can use the filters
interface to filter the search results.
Filters are available for numeric, boolean, string, enum, and geopoint properties. Depending on the type of the property, you can use different operators.
Filtering by string properties
On string
properties it performs an exact matching on tokens so it is advised to disable stemming for the properties
you want to use filters on (when using the default tokenizer you can provide the stemmerSkipProperties
configuration property).
If we consider the following schema:
{ "title": "string", "tag": "string",}
const results = client.search({ term: "prestige", where: { tag: "new", },});
print("coming soon")
val searchParams = SearchParams.builder( term = "prestige", ) .where(listOf( Condition("tag", ConditionType.Equals("new")) )) .build()
val results = client.search(searchParams, MyDoc.serializer())
print("coming soon")
use OramaCloud\Client;use OramaCloud\Client\Query;use OramaCloud\Client\QueryParams\WhereOperator;
$query = (new Query()) ->term('prestige') ->where('tag', WhereOperator::EQ, 'new');
$results = $client->search($query);
The results
will contain all documents that contain the word prestige
in the title
property and have tags
property equal to new
.
You can also specify a list of string, in this case, it will return all documents that contain at least one of the values provided:
const results = await client.search({ term: "prestige", where: { tag: ["favorite", "new"], },});
Filtering by number
properties
The number
properties supports the following operators:
Operator | Description | Example |
---|---|---|
gt | Greater than | year: { gt: 2000 } |
gte | Greater than or equal to | year: { gte: 2000 } |
lt | Less than | year: { lt: 2000 } |
lte | Less than or equal to | year: { lte: 2000 } |
eq | Equal to | year: { eq: 2000 } |
between | Between two values (inclusive) | year: { between: [2000, 2008] } |
So, considering the following schema:
{ "title": "string", "year": "number", "meta": { "rating": "number", "length": "number", "favorite": "boolean", "tags": "string", }}
You can filter the results by the meta.rating
property as follows:
const results = await client.search({ term: "prestige", where: { year: { gte: 2000, }, "meta.rating": { between: [5, 10], }, "meta.length": { lte: 60, }, },});
print("coming soon")
val searchParams = SearchParams.builder( term = "prestige",).where(listOf( Condition("year", ConditionType.GreaterThanOrEqual(2000.00)), Condition("meta.rating", ConditionType.Between( listOf(5.00, 10.00) )), Condition("meta.length", ConditionType.LessThanOrEqual(60.00)))).build()
val results = client.search(searchParams, MyDoc.serializer())
print("coming soon")
use OramaCloud\Client;use OramaCloud\Client\Query;use OramaCloud\Client\QueryParams\WhereOperator;
$query = (new Query()) ->term('prestige') ->where('year', WhereOperator::GTE, 2000) ->where('meta.rating', WhereOperator::BETWEEN, [5, 10]) ->where('meta.length', WhereOperator::LTE, 60);
$results = $client->search($query);
Filtering by boolean properties
For boolean properties, you can simply set the property to true
or false
:
const results = await client.search({ term: "prestige", where: { "meta.favorite": true, },});
print("coming soon")
print("coming soon")
print("coming soon")
use OramaCloud\Client;use OramaCloud\Client\Query;use OramaCloud\Client\QueryParams\WhereOperator;
$query = (new Query()) ->term('prestige') ->where('meta.favorite', WhereOperator::EQ, true);
$results = $client->search($query);
Filtering by enum properties
The enum properties support the following operators:
Operator | Description | Example |
---|---|---|
eq | Equal to | genre: { eq: 'drama' } |
in | Contained in the given array | genre: { in: ['drama', 'horror'] } |
nin | Not contained in the given array | genre: { nin: ['comedy'] } |
Filtering by Enum[] properties
The enum properties support the following operators:
Operator | Description | Example |
---|---|---|
containsAll | Contains all the given values | genre: { containsAll: ['comedy', 'action'] } |
containsAny | Contains any of the given values | genre: { containsAny: ['comedy', 'action', 'horror'] } |
Geosearch
Geosearch is a feature that allows you to filter your search results by distance from a given location, or by bounding box.
To perform geosearch queries, you first have to define a new geopoint
property inside your schema definition when creating your Orama instance:
{ "name": "string", "location": "geopoint",}
What are geopoints
A geopoint is an object with two properties: lat
and lon
, which are both numbers. For reference, the following is a valid geopoint:
{ "lat": 45.46409, "lon": 9.19192}
As you may guess, lat
stands for latitude, and lon
for longitude. The values are expressed in degrees, and can be positive or negative.
Inserting documents with geopoints
When inserting documents containing geopoints, you’ll have to provide the lat
and lon
properties as floating-point numbers. For example, considering the following schema:
{ "name": "string", "location": "geopoint"}
We would have to insert the following docs:
[ { "name": "Duomo di Milano", "location": { "lat": 45.46409, "lon": 9.19192 } }, { "name": "Piazza Duomo", "location": { "lat": 45.46416, "lon": 9.18945 } }, { "name": "Piazzetta Reale", "location": { "lat": 45.46339, "lon": 9.19092 } }]
Given the points above, we can picture them on a map as follows:
To avoid confusion, please note that Orama is a full-text and vector search library. We do not provide the tools to draw on maps.
Now that we have some data, let’s see how we can perform geosearch queries.
Performing geosearch queries
When performing geosearch queries, we have to decide whether we want to filter our results by distance from a given location, or by bounding polygon.
When filtering by distance, we select a central coordinate and a radius of a given length, and we return all the documents that are within that radius from the central coordinate.
When filtering by bounding polygon, we select a polygon on the map, and we return all the documents that are within that polygon.
Filtering by distance (radius)
To filter by distance, we use the radius
property. Let’s see how it works:
const searchResult = await client.search({ term: 'Duomo', where: { location: { // The property we want to filter by radius: { // The filter we want to apply (in that case: "radius") coordinates: { // The central coordinate lat: 45.4648, lon: 9.18998 }, unit: 'm', // The unit of measurement. The default is "m" (meters) value: 1000, // The radius length. In that case, 1km } } }})
print("coming soon")
print("coming soon")
print("coming soon")
// coming soon
The geopoint { lat: 45.4648, lon: 9.18998 }
represents the entrance of the Vittorio Emanuele II Gallery in Milan, nearby the Duomo.
If we follow the configuration above, we are asking Orama to return all the documents that are within 100 meters from the Gallery, as shown in the following map:
The query above will return only one result indeed: the Piazza Duomo document.
If we change the inside
property to false
, we will get the opposite result: all the documents that are outside the radius.
Supported units of measurement
Orama currently supports the following units of measurement:
cm
(centimeters)m
(meters)km
(kilometers)ft
(feet)yd
(yards)mi
(miles)
All these units will be converted into meters automatically. If you feel like we should support other units of measurement, please open an issue
Filtering by bounding polygon
To filter by bounding polygon, we use the polygon
property. Let’s see how it works:
const searchResult = search(db, { term: 'Duomo', where: { location: { // The property we want to filter by polygon: { // The filter we want to apply (in that case: "polygon") coordinates: [ // The polygon coordinate { lat: 45.46472, lon: 9.1886 }, { lat: 45.46352, lon: 9.19177 }, { lat: 45.46278, lon: 9.19176 }, { lat: 45.4628, lon: 9.18857 }, { lat: 45.46472, lon: 9.1886 }, ] } } }})
print("coming soon")
print("coming soon")
print("coming soon")
// coming soon
If we try to draw the polygon above on our map, we will get the following result:
The query above will return only one result: Piazza Duomo. This is because it will filter all the documents inside the polygon whose name
property contains the term "Duomo"
.
If you want to return all the points outside the polygon, you can set the inside
property to false
.