Skip to content

Commit bcc931a

Browse files
Merge #726
726: Add exhaustive facet search r=curquiza a=kumarUjjawal # Pull Request ## Related issue Fixes #664 ## What does this PR do? - ## PR checklist Please check if your PR fulfills the following requirements: - [x] Does this PR fix an existing issue, or have you listed the changes applied in the PR description (and why they are needed)? - [x] Have you read the contributing guidelines? - [x] Have you made sure that the title is accurate and descriptive of the changes? Thank you so much for contributing to Meilisearch! <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Results now include a flag indicating whether facet counts are exact or estimated, so callers can know when displayed facet totals are definitive. * Added an optional query setting and a builder-style method to request exhaustive (exact) facet counts when performing searches, giving users control over accuracy vs. performance. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Kumar Ujjawal <[email protected]>
2 parents d725a3c + db0762e commit bcc931a

File tree

1 file changed

+79
-0
lines changed

1 file changed

+79
-0
lines changed

src/search.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ pub struct SearchResults<T> {
115115
pub facet_distribution: Option<HashMap<String, HashMap<String, usize>>>,
116116
/// facet stats of the numerical facets requested in the `facet` search parameter.
117117
pub facet_stats: Option<HashMap<String, FacetStats>>,
118+
/// Indicates whether facet counts are exhaustive (exact) rather than estimated.
119+
/// Present when the `exhaustiveFacetCount` search parameter is used.
120+
pub exhaustive_facet_count: Option<bool>,
118121
/// Processing time of the query.
119122
pub processing_time_ms: usize,
120123
/// Query originating the response.
@@ -408,6 +411,13 @@ pub struct SearchQuery<'a, Http: HttpClient> {
408411
#[serde(skip_serializing_if = "Option::is_none")]
409412
pub retrieve_vectors: Option<bool>,
410413

414+
/// Request exhaustive facet counts up to the limit defined by `maxTotalHits`.
415+
///
416+
/// When set to `true`, Meilisearch computes exact facet counts instead of approximate ones.
417+
/// Default is `false`.
418+
#[serde(skip_serializing_if = "Option::is_none")]
419+
pub exhaustive_facet_count: Option<bool>,
420+
411421
#[serde(skip_serializing_if = "Option::is_none")]
412422
pub(crate) federation_options: Option<QueryFederationOptions>,
413423
}
@@ -453,6 +463,7 @@ impl<'a, Http: HttpClient> SearchQuery<'a, Http> {
453463
hybrid: None,
454464
vector: None,
455465
retrieve_vectors: None,
466+
exhaustive_facet_count: None,
456467
distinct: None,
457468
ranking_score_threshold: None,
458469
locales: None,
@@ -721,6 +732,15 @@ impl<'a, Http: HttpClient> SearchQuery<'a, Http> {
721732
self.clone()
722733
}
723734

735+
/// Request exhaustive facet count in the response.
736+
pub fn with_exhaustive_facet_count<'b>(
737+
&'b mut self,
738+
exhaustive: bool,
739+
) -> &'b mut SearchQuery<'a, Http> {
740+
self.exhaustive_facet_count = Some(exhaustive);
741+
self
742+
}
743+
724744
/// Execute the query and fetch the results.
725745
pub async fn execute<T: 'static + DeserializeOwned + Send + Sync>(
726746
&'a self,
@@ -1090,6 +1110,7 @@ pub struct FacetSearchResponse {
10901110

10911111
#[cfg(test)]
10921112
pub(crate) mod tests {
1113+
use crate::errors::{ErrorCode, MeilisearchError};
10931114
use crate::{
10941115
client::*,
10951116
key::{Action, KeyBuilder},
@@ -1968,6 +1989,64 @@ pub(crate) mod tests {
19681989
Ok(())
19691990
}
19701991

1992+
#[meilisearch_test]
1993+
async fn test_search_with_exhaustive_facet_count(
1994+
client: Client,
1995+
index: Index,
1996+
) -> Result<(), Error> {
1997+
setup_test_index(&client, &index).await?;
1998+
1999+
// Request exhaustive facet counts for a specific facet and ensure the server
2000+
// returns the exhaustive flag in the response.
2001+
let mut query = SearchQuery::new(&index);
2002+
query
2003+
.with_facets(Selectors::Some(&["kind"]))
2004+
.with_exhaustive_facet_count(true);
2005+
2006+
let res = index.execute_query::<Document>(&query).await;
2007+
match res {
2008+
Ok(results) => {
2009+
assert!(results.exhaustive_facet_count.is_some());
2010+
Ok(())
2011+
}
2012+
Err(error)
2013+
if matches!(
2014+
error,
2015+
Error::Meilisearch(MeilisearchError {
2016+
error_code: ErrorCode::BadRequest,
2017+
..
2018+
})
2019+
) =>
2020+
{
2021+
// Server doesn't support this field on /search yet; treat as a skip.
2022+
Ok(())
2023+
}
2024+
Err(e) => Err(e),
2025+
}
2026+
}
2027+
2028+
#[test]
2029+
fn test_search_query_serialization_exhaustive_facet_count() {
2030+
// Build a query and ensure it serializes using the expected camelCase field name
2031+
let client = Client::new(
2032+
option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700"),
2033+
Some(option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey")),
2034+
)
2035+
.unwrap();
2036+
let index = client.index("dummy");
2037+
2038+
let mut query = SearchQuery::new(&index);
2039+
query
2040+
.with_facets(Selectors::Some(&["kind"]))
2041+
.with_exhaustive_facet_count(true);
2042+
2043+
let v = serde_json::to_value(&query).unwrap();
2044+
assert_eq!(
2045+
v.get("exhaustiveFacetCount").and_then(|b| b.as_bool()),
2046+
Some(true)
2047+
);
2048+
}
2049+
19712050
#[meilisearch_test]
19722051
async fn test_facet_search_with_facet_query(client: Client, index: Index) -> Result<(), Error> {
19732052
setup_test_index(&client, &index).await?;

0 commit comments

Comments
 (0)