Skip to content

Add multi search method for v1.1.0 of Meilisearch #454

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 3, 2023
Merged
Show file tree
Hide file tree
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
64 changes: 63 additions & 1 deletion src/client.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use serde::{Deserialize, Serialize};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::{json, Value};
use std::{collections::HashMap, time::Duration};
use time::OffsetDateTime;
Expand All @@ -8,6 +8,7 @@ use crate::{
indexes::*,
key::{Key, KeyBuilder, KeyUpdater, KeysQuery, KeysResults},
request::*,
search::*,
task_info::TaskInfo,
tasks::{Task, TasksCancelQuery, TasksDeleteQuery, TasksResults, TasksSearchQuery},
utils::async_sleep,
Expand Down Expand Up @@ -63,6 +64,67 @@ impl Client {
Ok(indexes_results)
}

pub async fn execute_multi_search_query<T: 'static + DeserializeOwned>(
&self,
body: &MultiSearchQuery<'_, '_>,
) -> Result<MultiSearchResponse<T>, Error> {
request::<(), &MultiSearchQuery, MultiSearchResponse<T>>(
&format!("{}/multi-search", &self.host),
&self.api_key,
Method::Post { body, query: () },
200,
)
.await
}

/// Make multiple search requests.
///
/// # Example
///
/// ```
/// use serde::{Serialize, Deserialize};
/// # use meilisearch_sdk::{client::*, indexes::*, search::*};
///
/// #
/// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
/// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
/// #
/// #[derive(Serialize, Deserialize, Debug)]
/// struct Movie {
/// name: String,
/// description: String,
/// }
///
/// # futures::executor::block_on(async move {
/// let client = Client::new(MEILISEARCH_URL, MEILISEARCH_API_KEY);
/// let mut movies = client.index("search");
///
/// // add some documents
/// # movies.add_or_replace(&[Movie{name:String::from("Interstellar"), description:String::from("Interstellar chronicles the adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.")},Movie{name:String::from("Unknown"), description:String::from("Unknown")}], Some("name")).await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
///
/// let search_query_1 = SearchQuery::new(&movies)
/// .with_query("Interstellar")
/// .build();
/// let search_query_2 = SearchQuery::new(&movies)
/// .with_query("")
/// .build();
///
/// let response = client
/// .multi_search()
/// .with_search_query(search_query_1)
/// .with_search_query(search_query_2)
/// .execute::<Movie>()
/// .await
/// .unwrap();
///
/// assert_eq!(response.results.len(), 2);
/// # movies.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
/// # });
/// ```
pub fn multi_search(&self) -> MultiSearchQuery {
MultiSearchQuery::new(self)
}

/// Return the host associated with this index.
///
/// # Example
Expand Down
75 changes: 72 additions & 3 deletions src/search.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::{errors::Error, indexes::Index};
use crate::{client::Client, errors::Error, indexes::Index};
use either::Either;
use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer};
use serde_json::{Map, Value};
use std::collections::HashMap;

#[derive(Deserialize, Debug, Eq, PartialEq)]
#[derive(Deserialize, Debug, Eq, PartialEq, Clone)]
pub struct MatchRange {
pub start: usize,
pub length: usize,
Expand Down Expand Up @@ -33,7 +33,7 @@ pub enum MatchingStrategies {

/// A single result.
/// Contains the complete object, optionally the formatted object, and optionally an object that contains information about the matches.
#[derive(Deserialize, Debug)]
#[derive(Deserialize, Debug, Clone)]
pub struct SearchResult<T> {
/// The full result.
#[serde(flatten)]
Expand Down Expand Up @@ -80,6 +80,8 @@ pub struct SearchResults<T> {
pub processing_time_ms: usize,
/// Query originating the response
pub query: String,
/// Index uid on which the search was made
pub index_uid: Option<String>,
}

fn serialize_with_wildcard<S: Serializer, T: Serialize>(
Expand Down Expand Up @@ -287,6 +289,9 @@ pub struct SearchQuery<'a> {
/// Defines the strategy on how to handle queries containing multiple words.
#[serde(skip_serializing_if = "Option::is_none")]
pub matching_strategy: Option<MatchingStrategies>,

#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) index_uid: Option<&'a str>,
}

#[allow(missing_docs)]
Expand All @@ -311,6 +316,7 @@ impl<'a> SearchQuery<'a> {
highlight_post_tag: None,
show_matches_position: None,
matching_strategy: None,
index_uid: None,
}
}
pub fn with_query<'b>(&'b mut self, query: &'a str) -> &'b mut SearchQuery<'a> {
Expand Down Expand Up @@ -461,6 +467,10 @@ impl<'a> SearchQuery<'a> {
self.matching_strategy = Some(matching_strategy);
self
}
pub fn with_index_uid<'b>(&'b mut self) -> &'b mut SearchQuery<'a> {
self.index_uid = Some(&self.index.uid);
self
}
pub fn build(&mut self) -> SearchQuery<'a> {
self.clone()
}
Expand All @@ -472,6 +482,43 @@ impl<'a> SearchQuery<'a> {
}
}

#[derive(Debug, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct MultiSearchQuery<'a, 'b> {
#[serde(skip_serializing)]
client: &'a Client,
pub queries: Vec<SearchQuery<'b>>,
}

#[allow(missing_docs)]
impl<'a, 'b> MultiSearchQuery<'a, 'b> {
pub fn new(client: &'a Client) -> MultiSearchQuery<'a, 'b> {
MultiSearchQuery {
client,
queries: Vec::new(),
}
}
pub fn with_search_query(
&mut self,
mut search_query: SearchQuery<'b>,
) -> &mut MultiSearchQuery<'a, 'b> {
search_query.with_index_uid();
self.queries.push(search_query);
self
}

/// Execute the query and fetch the results.
pub async fn execute<T: 'static + DeserializeOwned>(
&'a self,
) -> Result<MultiSearchResponse<T>, Error> {
self.client.execute_multi_search_query::<T>(self).await
}
}
#[derive(Debug, Deserialize)]
pub struct MultiSearchResponse<T> {
pub results: Vec<SearchResults<T>>,
}

#[cfg(test)]
mod tests {
use crate::{client::*, search::*};
Expand Down Expand Up @@ -524,6 +571,28 @@ mod tests {
Ok(())
}

#[meilisearch_test]
async fn test_multi_search(client: Client, index: Index) -> Result<(), Error> {
setup_test_index(&client, &index).await?;
let search_query_1 = SearchQuery::new(&index)
.with_query("Sorcerer's Stone")
.build();
let search_query_2 = SearchQuery::new(&index)
.with_query("Chamber of Secrets")
.build();

let response = client
.multi_search()
.with_search_query(search_query_1)
.with_search_query(search_query_2)
.execute::<Document>()
.await
.unwrap();

assert_eq!(response.results.len(), 2);
Ok(())
}

#[meilisearch_test]
async fn test_query_builder(_client: Client, index: Index) -> Result<(), Error> {
let mut query = SearchQuery::new(&index);
Expand Down