Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions src/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ pub struct SearchResults<T> {
pub facet_distribution: Option<HashMap<String, HashMap<String, usize>>>,
/// facet stats of the numerical facets requested in the `facet` search parameter.
pub facet_stats: Option<HashMap<String, FacetStats>>,
/// Indicates whether facet counts are exhaustive (exact) rather than estimated.
/// Present when the `exhaustiveFacetCount` search parameter is used.
pub exhaustive_facet_count: Option<bool>,
/// Processing time of the query.
pub processing_time_ms: usize,
/// Query originating the response.
Expand Down Expand Up @@ -408,6 +411,13 @@ pub struct SearchQuery<'a, Http: HttpClient> {
#[serde(skip_serializing_if = "Option::is_none")]
pub retrieve_vectors: Option<bool>,

/// Request exhaustive facet counts up to the limit defined by `maxTotalHits`.
///
/// When set to `true`, Meilisearch computes exact facet counts instead of approximate ones.
/// Default is `false`.
#[serde(skip_serializing_if = "Option::is_none")]
pub exhaustive_facet_count: Option<bool>,

#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) federation_options: Option<QueryFederationOptions>,
}
Expand Down Expand Up @@ -453,6 +463,7 @@ impl<'a, Http: HttpClient> SearchQuery<'a, Http> {
hybrid: None,
vector: None,
retrieve_vectors: None,
exhaustive_facet_count: None,
distinct: None,
ranking_score_threshold: None,
locales: None,
Expand Down Expand Up @@ -721,6 +732,15 @@ impl<'a, Http: HttpClient> SearchQuery<'a, Http> {
self.clone()
}

/// Request exhaustive facet count in the response.
pub fn with_exhaustive_facet_count<'b>(
&'b mut self,
exhaustive: bool,
) -> &'b mut SearchQuery<'a, Http> {
self.exhaustive_facet_count = Some(exhaustive);
self
}

/// Execute the query and fetch the results.
pub async fn execute<T: 'static + DeserializeOwned + Send + Sync>(
&'a self,
Expand Down Expand Up @@ -1090,6 +1110,7 @@ pub struct FacetSearchResponse {

#[cfg(test)]
pub(crate) mod tests {
use crate::errors::{ErrorCode, MeilisearchError};
use crate::{
client::*,
key::{Action, KeyBuilder},
Expand Down Expand Up @@ -1968,6 +1989,64 @@ pub(crate) mod tests {
Ok(())
}

#[meilisearch_test]
async fn test_search_with_exhaustive_facet_count(
client: Client,
index: Index,
) -> Result<(), Error> {
setup_test_index(&client, &index).await?;

// Request exhaustive facet counts for a specific facet and ensure the server
// returns the exhaustive flag in the response.
let mut query = SearchQuery::new(&index);
query
.with_facets(Selectors::Some(&["kind"]))
.with_exhaustive_facet_count(true);

let res = index.execute_query::<Document>(&query).await;
match res {
Ok(results) => {
assert!(results.exhaustive_facet_count.is_some());
Ok(())
}
Err(error)
if matches!(
error,
Error::Meilisearch(MeilisearchError {
error_code: ErrorCode::BadRequest,
..
})
) =>
{
// Server doesn't support this field on /search yet; treat as a skip.
Ok(())
}
Err(e) => Err(e),
}
}

#[test]
fn test_search_query_serialization_exhaustive_facet_count() {
// Build a query and ensure it serializes using the expected camelCase field name
let client = Client::new(
option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700"),
Some(option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey")),
)
.unwrap();
let index = client.index("dummy");

let mut query = SearchQuery::new(&index);
query
.with_facets(Selectors::Some(&["kind"]))
.with_exhaustive_facet_count(true);

let v = serde_json::to_value(&query).unwrap();
assert_eq!(
v.get("exhaustiveFacetCount").and_then(|b| b.as_bool()),
Some(true)
);
}

#[meilisearch_test]
async fn test_facet_search_with_facet_query(client: Client, index: Index) -> Result<(), Error> {
setup_test_index(&client, &index).await?;
Expand Down