1use crate::{
2 client::Client,
3 errors::{Error, MeilisearchError},
4 indexes::Index,
5 request::HttpClient,
6 DefaultHttpClient,
7};
8use either::Either;
9use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer};
10use serde_json::{Map, Value};
11use std::collections::HashMap;
12
13#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
14pub struct MatchRange {
15 pub start: usize,
16 pub length: usize,
17
18 pub indices: Option<Vec<usize>>,
28}
29
30#[derive(Serialize, Debug, Eq, PartialEq, Clone)]
31#[serde(transparent)]
32pub struct Filter<'a> {
33 #[serde(with = "either::serde_untagged")]
34 inner: Either<&'a str, Vec<&'a str>>,
35}
36
37impl<'a> Filter<'a> {
38 #[must_use]
39 pub fn new(inner: Either<&'a str, Vec<&'a str>>) -> Filter<'a> {
40 Filter { inner }
41 }
42}
43
44#[derive(Debug, Clone, Serialize)]
45pub enum MatchingStrategies {
46 #[serde(rename = "all")]
47 ALL,
48 #[serde(rename = "last")]
49 LAST,
50 #[serde(rename = "frequency")]
51 FREQUENCY,
52}
53
54#[derive(Serialize, Deserialize, Debug, Clone)]
58pub struct SearchResult<T> {
59 #[serde(flatten)]
61 pub result: T,
62
63 #[serde(rename = "_formatted", skip_serializing_if = "Option::is_none")]
65 pub formatted_result: Option<Map<String, Value>>,
66
67 #[serde(rename = "_matchesPosition", skip_serializing_if = "Option::is_none")]
69 pub matches_position: Option<HashMap<String, Vec<MatchRange>>>,
70
71 #[serde(rename = "_rankingScore", skip_serializing_if = "Option::is_none")]
73 pub ranking_score: Option<f64>,
74
75 #[serde(
77 rename = "_rankingScoreDetails",
78 skip_serializing_if = "Option::is_none"
79 )]
80 pub ranking_score_details: Option<Map<String, Value>>,
81
82 #[serde(rename = "_federation", skip_serializing_if = "Option::is_none")]
84 pub federation: Option<FederationHitInfo>,
85}
86
87#[derive(Serialize, Deserialize, Debug, Clone)]
88#[serde(rename_all = "camelCase")]
89pub struct FacetStats {
90 pub min: f64,
91 pub max: f64,
92}
93
94#[derive(Serialize, Deserialize, Debug, Clone)]
95#[serde(rename_all = "camelCase")]
96pub struct SearchResults<T> {
98 pub hits: Vec<SearchResult<T>>,
100 pub offset: Option<usize>,
102 pub limit: Option<usize>,
104 pub estimated_total_hits: Option<usize>,
106 pub page: Option<usize>,
108 pub hits_per_page: Option<usize>,
110 pub total_hits: Option<usize>,
112 pub total_pages: Option<usize>,
114 pub facet_distribution: Option<HashMap<String, HashMap<String, usize>>>,
116 pub facet_stats: Option<HashMap<String, FacetStats>>,
118 pub exhaustive_facet_count: Option<bool>,
121 pub processing_time_ms: usize,
123 pub query: String,
125 pub index_uid: Option<String>,
127 #[serde(
130 rename = "queryVector",
131 alias = "query_vector",
132 alias = "queryEmbedding",
133 alias = "query_embedding",
134 alias = "vector",
135 skip_serializing_if = "Option::is_none"
136 )]
137 pub query_vector: Option<Vec<f32>>,
138}
139
140fn serialize_attributes_to_crop_with_wildcard<S: Serializer>(
141 data: &Option<Selectors<&[AttributeToCrop]>>,
142 s: S,
143) -> Result<S::Ok, S::Error> {
144 match data {
145 Some(Selectors::All) => ["*"].serialize(s),
146 Some(Selectors::Some(data)) => {
147 let results = data
148 .iter()
149 .map(|(name, value)| {
150 let mut result = (*name).to_string();
151 if let Some(value) = value {
152 result.push(':');
153 result.push_str(value.to_string().as_str());
154 }
155 result
156 })
157 .collect::<Vec<_>>();
158 results.serialize(s)
159 }
160 None => s.serialize_none(),
161 }
162}
163
164#[derive(Debug, Clone)]
168pub enum Selectors<T> {
169 Some(T),
171 All,
173}
174
175impl<T: Serialize> Serialize for Selectors<T> {
176 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
177 match self {
178 Selectors::Some(data) => data.serialize(s),
179 Selectors::All => ["*"].serialize(s),
180 }
181 }
182}
183
184#[derive(Debug, Serialize, Clone)]
186#[serde(rename_all = "camelCase")]
187pub struct HybridSearch<'a> {
188 pub embedder: &'a str,
190 pub semantic_ratio: f32,
194}
195
196type AttributeToCrop<'a> = (&'a str, Option<usize>);
197
198#[derive(Debug, Serialize, Clone)]
257#[serde(rename_all = "camelCase")]
258pub struct SearchQuery<'a, Http: HttpClient> {
259 #[serde(skip_serializing)]
260 index: &'a Index<Http>,
261 #[serde(skip_serializing_if = "Option::is_none")]
263 #[serde(rename = "q")]
264 pub query: Option<&'a str>,
265 #[serde(skip_serializing_if = "Option::is_none")]
272 pub offset: Option<usize>,
273 #[serde(skip_serializing_if = "Option::is_none")]
282 pub limit: Option<usize>,
283 #[serde(skip_serializing_if = "Option::is_none")]
289 pub page: Option<usize>,
290 #[serde(skip_serializing_if = "Option::is_none")]
294 pub hits_per_page: Option<usize>,
295 #[serde(skip_serializing_if = "Option::is_none")]
299 pub filter: Option<Filter<'a>>,
300 #[serde(skip_serializing_if = "Option::is_none")]
306 pub facets: Option<Selectors<&'a [&'a str]>>,
307 #[serde(skip_serializing_if = "Option::is_none")]
309 pub sort: Option<&'a [&'a str]>,
310 #[serde(skip_serializing_if = "Option::is_none")]
316 pub attributes_to_search_on: Option<&'a [&'a str]>,
317 #[serde(skip_serializing_if = "Option::is_none")]
323 pub attributes_to_retrieve: Option<Selectors<&'a [&'a str]>>,
324 #[serde(skip_serializing_if = "Option::is_none")]
330 #[serde(serialize_with = "serialize_attributes_to_crop_with_wildcard")]
331 pub attributes_to_crop: Option<Selectors<&'a [AttributeToCrop<'a>]>>,
332 #[serde(skip_serializing_if = "Option::is_none")]
338 pub crop_length: Option<usize>,
339 #[serde(skip_serializing_if = "Option::is_none")]
345 pub crop_marker: Option<&'a str>,
346 #[serde(skip_serializing_if = "Option::is_none")]
350 pub attributes_to_highlight: Option<Selectors<&'a [&'a str]>>,
351 #[serde(skip_serializing_if = "Option::is_none")]
357 pub highlight_pre_tag: Option<&'a str>,
358 #[serde(skip_serializing_if = "Option::is_none")]
364 pub highlight_post_tag: Option<&'a str>,
365 #[serde(skip_serializing_if = "Option::is_none")]
369 pub show_matches_position: Option<bool>,
370
371 #[serde(skip_serializing_if = "Option::is_none")]
375 pub show_ranking_score: Option<bool>,
376
377 #[serde(skip_serializing_if = "Option::is_none")]
381 pub show_ranking_score_details: Option<bool>,
382
383 #[serde(skip_serializing_if = "Option::is_none")]
385 pub matching_strategy: Option<MatchingStrategies>,
386
387 #[serde(skip_serializing_if = "Option::is_none")]
389 pub distinct: Option<&'a str>,
390
391 #[serde(skip_serializing_if = "Option::is_none")]
393 pub ranking_score_threshold: Option<f64>,
394
395 #[serde(skip_serializing_if = "Option::is_none")]
397 pub locales: Option<&'a [&'a str]>,
398
399 #[serde(skip_serializing_if = "Option::is_none")]
400 pub(crate) index_uid: Option<&'a str>,
401
402 #[serde(skip_serializing_if = "Option::is_none")]
404 pub hybrid: Option<HybridSearch<'a>>,
405
406 #[serde(skip_serializing_if = "Option::is_none")]
408 pub vector: Option<&'a [f32]>,
409
410 #[serde(skip_serializing_if = "Option::is_none")]
412 pub retrieve_vectors: Option<bool>,
413
414 #[serde(skip_serializing_if = "Option::is_none")]
416 pub media: Option<Value>,
417
418 #[serde(skip_serializing_if = "Option::is_none")]
423 pub exhaustive_facet_count: Option<bool>,
424
425 #[serde(skip_serializing_if = "Option::is_none")]
426 pub(crate) federation_options: Option<QueryFederationOptions>,
427}
428
429#[derive(Debug, Serialize, Clone)]
430#[serde(rename_all = "camelCase")]
431pub struct QueryFederationOptions {
432 #[serde(skip_serializing_if = "Option::is_none")]
434 pub weight: Option<f32>,
435 #[serde(skip_serializing_if = "Option::is_none")]
437 pub remote: Option<String>,
438}
439
440#[allow(missing_docs)]
441impl<'a, Http: HttpClient> SearchQuery<'a, Http> {
442 #[must_use]
443 pub fn new(index: &'a Index<Http>) -> SearchQuery<'a, Http> {
444 SearchQuery {
445 index,
446 query: None,
447 offset: None,
448 limit: None,
449 page: None,
450 hits_per_page: None,
451 filter: None,
452 sort: None,
453 facets: None,
454 attributes_to_search_on: None,
455 attributes_to_retrieve: None,
456 attributes_to_crop: None,
457 crop_length: None,
458 crop_marker: None,
459 attributes_to_highlight: None,
460 highlight_pre_tag: None,
461 highlight_post_tag: None,
462 show_matches_position: None,
463 show_ranking_score: None,
464 show_ranking_score_details: None,
465 matching_strategy: None,
466 index_uid: None,
467 hybrid: None,
468 vector: None,
469 retrieve_vectors: None,
470 media: None,
471 exhaustive_facet_count: None,
472 distinct: None,
473 ranking_score_threshold: None,
474 locales: None,
475 federation_options: None,
476 }
477 }
478
479 pub fn with_query<'b>(&'b mut self, query: &'a str) -> &'b mut SearchQuery<'a, Http> {
480 self.query = Some(query);
481 self
482 }
483
484 pub fn with_offset<'b>(&'b mut self, offset: usize) -> &'b mut SearchQuery<'a, Http> {
485 self.offset = Some(offset);
486 self
487 }
488
489 pub fn with_limit<'b>(&'b mut self, limit: usize) -> &'b mut SearchQuery<'a, Http> {
490 self.limit = Some(limit);
491 self
492 }
493
494 pub fn with_page<'b>(&'b mut self, page: usize) -> &'b mut SearchQuery<'a, Http> {
522 self.page = Some(page);
523 self
524 }
525
526 pub fn with_hits_per_page<'b>(
554 &'b mut self,
555 hits_per_page: usize,
556 ) -> &'b mut SearchQuery<'a, Http> {
557 self.hits_per_page = Some(hits_per_page);
558 self
559 }
560
561 pub fn with_filter<'b>(&'b mut self, filter: &'a str) -> &'b mut SearchQuery<'a, Http> {
562 self.filter = Some(Filter::new(Either::Left(filter)));
563 self
564 }
565
566 pub fn with_array_filter<'b>(
567 &'b mut self,
568 filter: Vec<&'a str>,
569 ) -> &'b mut SearchQuery<'a, Http> {
570 self.filter = Some(Filter::new(Either::Right(filter)));
571 self
572 }
573
574 pub fn with_retrieve_vectors<'b>(
576 &'b mut self,
577 retrieve_vectors: bool,
578 ) -> &'b mut SearchQuery<'a, Http> {
579 self.retrieve_vectors = Some(retrieve_vectors);
580 self
581 }
582
583 pub fn with_facets<'b>(
584 &'b mut self,
585 facets: Selectors<&'a [&'a str]>,
586 ) -> &'b mut SearchQuery<'a, Http> {
587 self.facets = Some(facets);
588 self
589 }
590
591 pub fn with_sort<'b>(&'b mut self, sort: &'a [&'a str]) -> &'b mut SearchQuery<'a, Http> {
592 self.sort = Some(sort);
593 self
594 }
595
596 pub fn with_attributes_to_search_on<'b>(
597 &'b mut self,
598 attributes_to_search_on: &'a [&'a str],
599 ) -> &'b mut SearchQuery<'a, Http> {
600 self.attributes_to_search_on = Some(attributes_to_search_on);
601 self
602 }
603
604 pub fn with_attributes_to_retrieve<'b>(
605 &'b mut self,
606 attributes_to_retrieve: Selectors<&'a [&'a str]>,
607 ) -> &'b mut SearchQuery<'a, Http> {
608 self.attributes_to_retrieve = Some(attributes_to_retrieve);
609 self
610 }
611
612 pub fn with_attributes_to_crop<'b>(
613 &'b mut self,
614 attributes_to_crop: Selectors<&'a [(&'a str, Option<usize>)]>,
615 ) -> &'b mut SearchQuery<'a, Http> {
616 self.attributes_to_crop = Some(attributes_to_crop);
617 self
618 }
619
620 pub fn with_crop_length<'b>(&'b mut self, crop_length: usize) -> &'b mut SearchQuery<'a, Http> {
621 self.crop_length = Some(crop_length);
622 self
623 }
624
625 pub fn with_crop_marker<'b>(
626 &'b mut self,
627 crop_marker: &'a str,
628 ) -> &'b mut SearchQuery<'a, Http> {
629 self.crop_marker = Some(crop_marker);
630 self
631 }
632
633 pub fn with_attributes_to_highlight<'b>(
634 &'b mut self,
635 attributes_to_highlight: Selectors<&'a [&'a str]>,
636 ) -> &'b mut SearchQuery<'a, Http> {
637 self.attributes_to_highlight = Some(attributes_to_highlight);
638 self
639 }
640
641 pub fn with_highlight_pre_tag<'b>(
642 &'b mut self,
643 highlight_pre_tag: &'a str,
644 ) -> &'b mut SearchQuery<'a, Http> {
645 self.highlight_pre_tag = Some(highlight_pre_tag);
646 self
647 }
648
649 pub fn with_highlight_post_tag<'b>(
650 &'b mut self,
651 highlight_post_tag: &'a str,
652 ) -> &'b mut SearchQuery<'a, Http> {
653 self.highlight_post_tag = Some(highlight_post_tag);
654 self
655 }
656
657 pub fn with_show_matches_position<'b>(
658 &'b mut self,
659 show_matches_position: bool,
660 ) -> &'b mut SearchQuery<'a, Http> {
661 self.show_matches_position = Some(show_matches_position);
662 self
663 }
664
665 pub fn with_show_ranking_score<'b>(
666 &'b mut self,
667 show_ranking_score: bool,
668 ) -> &'b mut SearchQuery<'a, Http> {
669 self.show_ranking_score = Some(show_ranking_score);
670 self
671 }
672
673 pub fn with_show_ranking_score_details<'b>(
674 &'b mut self,
675 show_ranking_score_details: bool,
676 ) -> &'b mut SearchQuery<'a, Http> {
677 self.show_ranking_score_details = Some(show_ranking_score_details);
678 self
679 }
680
681 pub fn with_matching_strategy<'b>(
682 &'b mut self,
683 matching_strategy: MatchingStrategies,
684 ) -> &'b mut SearchQuery<'a, Http> {
685 self.matching_strategy = Some(matching_strategy);
686 self
687 }
688
689 pub fn with_index_uid<'b>(&'b mut self) -> &'b mut SearchQuery<'a, Http> {
690 self.index_uid = Some(&self.index.uid);
691 self
692 }
693
694 pub fn with_hybrid<'b>(
696 &'b mut self,
697 embedder: &'a str,
698 semantic_ratio: f32,
699 ) -> &'b mut SearchQuery<'a, Http> {
700 self.hybrid = Some(HybridSearch {
701 embedder,
702 semantic_ratio,
703 });
704 self
705 }
706
707 pub fn with_vector<'b>(&'b mut self, vector: &'a [f32]) -> &'b mut SearchQuery<'a, Http> {
714 self.vector = Some(vector);
715 self
716 }
717
718 pub fn with_media<'b>(&'b mut self, media: Value) -> &'b mut SearchQuery<'a, Http> {
720 self.media = Some(media);
721 self
722 }
723
724 pub fn with_distinct<'b>(&'b mut self, distinct: &'a str) -> &'b mut SearchQuery<'a, Http> {
725 self.distinct = Some(distinct);
726 self
727 }
728
729 pub fn with_ranking_score_threshold<'b>(
730 &'b mut self,
731 ranking_score_threshold: f64,
732 ) -> &'b mut SearchQuery<'a, Http> {
733 self.ranking_score_threshold = Some(ranking_score_threshold);
734 self
735 }
736
737 pub fn with_locales<'b>(&'b mut self, locales: &'a [&'a str]) -> &'b mut SearchQuery<'a, Http> {
738 self.locales = Some(locales);
739 self
740 }
741
742 pub fn build(&mut self) -> SearchQuery<'a, Http> {
743 self.clone()
744 }
745
746 pub fn with_exhaustive_facet_count<'b>(
748 &'b mut self,
749 exhaustive: bool,
750 ) -> &'b mut SearchQuery<'a, Http> {
751 self.exhaustive_facet_count = Some(exhaustive);
752 self
753 }
754
755 pub async fn execute<T: 'static + DeserializeOwned + Send + Sync>(
757 &'a self,
758 ) -> Result<SearchResults<T>, Error> {
759 self.index.execute_query::<T>(self).await
760 }
761}
762
763#[derive(Debug, Serialize, Clone)]
764#[serde(rename_all = "camelCase")]
765pub struct MultiSearchQuery<'a, 'b, Http: HttpClient = DefaultHttpClient> {
766 #[serde(skip_serializing)]
767 client: &'a Client<Http>,
768 #[serde(bound(serialize = ""))]
773 pub queries: Vec<SearchQuery<'b, Http>>,
774}
775
776#[allow(missing_docs)]
777impl<'a, 'b, Http: HttpClient> MultiSearchQuery<'a, 'b, Http> {
778 #[must_use]
779 pub fn new(client: &'a Client<Http>) -> MultiSearchQuery<'a, 'b, Http> {
780 MultiSearchQuery {
781 client,
782 queries: Vec::new(),
783 }
784 }
785
786 pub fn with_search_query(
787 &mut self,
788 mut search_query: SearchQuery<'b, Http>,
789 ) -> &mut MultiSearchQuery<'a, 'b, Http> {
790 search_query.with_index_uid();
791 self.queries.push(search_query);
792 self
793 }
794
795 pub fn with_search_query_and_weight(
796 &mut self,
797 search_query: SearchQuery<'b, Http>,
798 weight: f32,
799 ) -> &mut MultiSearchQuery<'a, 'b, Http> {
800 self.with_search_query_and_options(
801 search_query,
802 QueryFederationOptions {
803 weight: Some(weight),
804 remote: None,
805 },
806 )
807 }
808
809 pub fn with_search_query_and_options(
810 &mut self,
811 mut search_query: SearchQuery<'b, Http>,
812 options: QueryFederationOptions,
813 ) -> &mut MultiSearchQuery<'a, 'b, Http> {
814 search_query.with_index_uid();
815 search_query.federation_options = Some(options);
816 self.queries.push(search_query);
817 self
818 }
819
820 pub fn with_federation(
822 self,
823 federation: FederationOptions,
824 ) -> FederatedMultiSearchQuery<'a, 'b, Http> {
825 FederatedMultiSearchQuery {
826 client: self.client,
827 queries: self.queries,
828 federation: Some(federation),
829 }
830 }
831
832 pub async fn execute<T: 'static + DeserializeOwned + Send + Sync>(
834 &'a self,
835 ) -> Result<MultiSearchResponse<T>, Error> {
836 self.client.execute_multi_search_query::<T>(self).await
837 }
838}
839
840#[derive(Debug, Clone, Deserialize, Serialize)]
841pub struct MultiSearchResponse<T> {
842 pub results: Vec<SearchResults<T>>,
843}
844
845#[derive(Debug, Serialize, Clone)]
846#[serde(rename_all = "camelCase")]
847pub struct FederatedMultiSearchQuery<'a, 'b, Http: HttpClient = DefaultHttpClient> {
848 #[serde(skip_serializing)]
849 client: &'a Client<Http>,
850 #[serde(bound(serialize = ""))]
851 pub queries: Vec<SearchQuery<'b, Http>>,
852 #[serde(skip_serializing_if = "Option::is_none")]
853 pub federation: Option<FederationOptions>,
854}
855
856#[derive(Debug, Serialize, Clone, Default)]
857#[serde(rename_all = "camelCase")]
858pub struct MergeFacets {
859 #[serde(skip_serializing_if = "Option::is_none")]
860 pub max_values_per_facet: Option<usize>,
861}
862
863#[derive(Debug, Serialize, Clone, Default)]
866#[serde(rename_all = "camelCase")]
867pub struct FederationOptions {
868 #[serde(skip_serializing_if = "Option::is_none")]
870 pub offset: Option<usize>,
871
872 #[serde(skip_serializing_if = "Option::is_none")]
874 pub limit: Option<usize>,
875
876 #[serde(skip_serializing_if = "Option::is_none")]
878 pub facets_by_index: Option<HashMap<String, Vec<String>>>,
879
880 #[serde(skip_serializing_if = "Option::is_none")]
882 pub merge_facets: Option<MergeFacets>,
883}
884
885impl<'a, Http: HttpClient> FederatedMultiSearchQuery<'a, '_, Http> {
886 pub async fn execute<T: 'static + DeserializeOwned + Send + Sync>(
888 &'a self,
889 ) -> Result<FederatedMultiSearchResponse<T>, Error> {
890 self.client
891 .execute_federated_multi_search_query::<T>(self)
892 .await
893 }
894}
895
896#[derive(Debug, Clone, Default, Serialize, Deserialize)]
897pub struct ComputedFacets {
898 pub distribution: HashMap<String, HashMap<String, u64>>,
899 pub stats: HashMap<String, FacetStats>,
900}
901
902#[derive(Debug, Deserialize, Clone)]
904#[serde(rename_all = "camelCase")]
905pub struct FederatedMultiSearchResponse<T> {
906 pub hits: Vec<SearchResult<T>>,
908
909 pub offset: usize,
911
912 pub limit: usize,
914
915 pub estimated_total_hits: usize,
917
918 pub processing_time_ms: usize,
920
921 pub facets_by_index: Option<ComputedFacets>,
923
924 pub facet_distribution: Option<HashMap<String, HashMap<String, usize>>>,
926
927 pub facet_stats: Option<HashMap<String, FacetStats>>,
929
930 pub remote_errors: Option<HashMap<String, MeilisearchError>>,
932}
933
934#[derive(Serialize, Deserialize, Debug, Clone)]
936#[serde(rename_all = "camelCase")]
937pub struct FederationHitInfo {
938 pub index_uid: String,
940
941 pub queries_position: usize,
943
944 pub remote: Option<String>,
946
947 pub weighted_ranking_score: f32,
949}
950
951#[derive(Debug, Serialize, Clone)]
1001#[serde(rename_all = "camelCase")]
1002pub struct FacetSearchQuery<'a, Http: HttpClient = DefaultHttpClient> {
1003 #[serde(skip_serializing)]
1004 index: &'a Index<Http>,
1005 pub facet_name: &'a str,
1007 #[serde(skip_serializing_if = "Option::is_none")]
1009 pub facet_query: Option<&'a str>,
1010 #[serde(skip_serializing_if = "Option::is_none")]
1012 #[serde(rename = "q")]
1013 pub search_query: Option<&'a str>,
1014 #[serde(skip_serializing_if = "Option::is_none")]
1018 pub filter: Option<Filter<'a>>,
1019 #[serde(skip_serializing_if = "Option::is_none")]
1021 pub matching_strategy: Option<MatchingStrategies>,
1022 #[serde(skip_serializing_if = "Option::is_none")]
1024 pub attributes_to_search_on: Option<&'a [&'a str]>,
1025 #[serde(skip_serializing_if = "Option::is_none")]
1027 pub exhaustive_facet_count: Option<bool>,
1028}
1029
1030#[allow(missing_docs)]
1031impl<'a, Http: HttpClient> FacetSearchQuery<'a, Http> {
1032 pub fn new(index: &'a Index<Http>, facet_name: &'a str) -> FacetSearchQuery<'a, Http> {
1033 FacetSearchQuery {
1034 index,
1035 facet_name,
1036 facet_query: None,
1037 search_query: None,
1038 filter: None,
1039 matching_strategy: None,
1040 attributes_to_search_on: None,
1041 exhaustive_facet_count: None,
1042 }
1043 }
1044
1045 pub fn with_facet_query<'b>(
1046 &'b mut self,
1047 facet_query: &'a str,
1048 ) -> &'b mut FacetSearchQuery<'a, Http> {
1049 self.facet_query = Some(facet_query);
1050 self
1051 }
1052
1053 pub fn with_search_query<'b>(
1054 &'b mut self,
1055 search_query: &'a str,
1056 ) -> &'b mut FacetSearchQuery<'a, Http> {
1057 self.search_query = Some(search_query);
1058 self
1059 }
1060
1061 pub fn with_filter<'b>(&'b mut self, filter: &'a str) -> &'b mut FacetSearchQuery<'a, Http> {
1062 self.filter = Some(Filter::new(Either::Left(filter)));
1063 self
1064 }
1065
1066 pub fn with_array_filter<'b>(
1067 &'b mut self,
1068 filter: Vec<&'a str>,
1069 ) -> &'b mut FacetSearchQuery<'a, Http> {
1070 self.filter = Some(Filter::new(Either::Right(filter)));
1071 self
1072 }
1073
1074 pub fn with_matching_strategy<'b>(
1075 &'b mut self,
1076 matching_strategy: MatchingStrategies,
1077 ) -> &'b mut FacetSearchQuery<'a, Http> {
1078 self.matching_strategy = Some(matching_strategy);
1079 self
1080 }
1081
1082 pub fn with_attributes_to_search_on<'b>(
1083 &'b mut self,
1084 attributes_to_search_on: &'a [&'a str],
1085 ) -> &'b mut FacetSearchQuery<'a, Http> {
1086 self.attributes_to_search_on = Some(attributes_to_search_on);
1087 self
1088 }
1089
1090 pub fn with_exhaustive_facet_count<'b>(
1091 &'b mut self,
1092 exhaustive_facet_count: bool,
1093 ) -> &'b mut FacetSearchQuery<'a, Http> {
1094 self.exhaustive_facet_count = Some(exhaustive_facet_count);
1095 self
1096 }
1097
1098 pub fn build(&mut self) -> FacetSearchQuery<'a, Http> {
1099 self.clone()
1100 }
1101
1102 pub async fn execute(&'a self) -> Result<FacetSearchResponse, Error> {
1103 self.index.execute_facet_query(self).await
1104 }
1105}
1106
1107#[derive(Debug, Deserialize)]
1108#[serde(rename_all = "camelCase")]
1109pub struct FacetHit {
1110 pub value: String,
1111 pub count: usize,
1112}
1113
1114#[derive(Debug, Deserialize)]
1115#[serde(rename_all = "camelCase")]
1116pub struct FacetSearchResponse {
1117 pub facet_hits: Vec<FacetHit>,
1118 pub facet_query: Option<String>,
1119 pub processing_time_ms: usize,
1120}
1121
1122#[cfg(test)]
1123pub(crate) mod tests {
1124 use crate::errors::{ErrorCode, MeilisearchError};
1125 use crate::{
1126 client::*,
1127 key::{Action, KeyBuilder},
1128 search::*,
1129 settings::EmbedderSource,
1130 };
1131 use big_s::S;
1132 use meilisearch_test_macro::meilisearch_test;
1133 use serde::{Deserialize, Serialize};
1134 use serde_json::{json, Map, Value};
1135
1136 #[test]
1137 fn search_query_serializes_media_parameter() {
1138 let client = Client::new("http://localhost:7700", Some("masterKey")).unwrap();
1139 let index = client.index("media_query");
1140 let mut query = SearchQuery::new(&index);
1141
1142 query.with_query("example").with_media(json!({
1143 "FIELD_A": "VALUE_A",
1144 "FIELD_B": {
1145 "FIELD_C": "VALUE_B",
1146 "FIELD_D": "VALUE_C"
1147 }
1148 }));
1149
1150 let serialized = serde_json::to_value(&query.build()).unwrap();
1151
1152 assert_eq!(
1153 serialized.get("media"),
1154 Some(&json!({
1155 "FIELD_A": "VALUE_A",
1156 "FIELD_B": {
1157 "FIELD_C": "VALUE_B",
1158 "FIELD_D": "VALUE_C"
1159 }
1160 }))
1161 );
1162 }
1163
1164 #[derive(Debug, Serialize, Deserialize, PartialEq)]
1165 pub struct Nested {
1166 child: String,
1167 }
1168
1169 #[derive(Debug, Serialize, Deserialize, PartialEq)]
1170 pub struct Document {
1171 pub id: usize,
1172 pub value: String,
1173 pub kind: String,
1174 pub number: i32,
1175 pub nested: Nested,
1176 #[serde(skip_serializing_if = "Option::is_none", default)]
1177 pub _vectors: Option<Vectors>,
1178 }
1179
1180 #[derive(Debug, Serialize, Deserialize, PartialEq)]
1181 struct Vector {
1182 embeddings: SingleOrMultipleVectors,
1183 regenerate: bool,
1184 }
1185
1186 #[derive(Serialize, Deserialize, Debug, PartialEq)]
1187 #[serde(untagged)]
1188 enum SingleOrMultipleVectors {
1189 Single(Vec<f32>),
1190 Multiple(Vec<Vec<f32>>),
1191 }
1192
1193 #[derive(Debug, Serialize, Deserialize, PartialEq)]
1194 pub struct Vectors(HashMap<String, Vector>);
1195
1196 impl<T: Into<Vec<f32>>> From<T> for Vectors {
1197 fn from(value: T) -> Self {
1198 let vec: Vec<f32> = value.into();
1199 Vectors(HashMap::from([(
1200 S("default"),
1201 Vector {
1202 embeddings: SingleOrMultipleVectors::Multiple(Vec::from([vec])),
1203 regenerate: false,
1204 },
1205 )]))
1206 }
1207 }
1208
1209 impl PartialEq<Map<String, Value>> for Document {
1210 #[allow(clippy::cmp_owned)]
1211 fn eq(&self, rhs: &Map<String, Value>) -> bool {
1212 self.id.to_string() == rhs["id"]
1213 && self.value == rhs["value"]
1214 && self.kind == rhs["kind"]
1215 }
1216 }
1217
1218 fn vectorize(is_harry_potter: bool, id: usize) -> Vec<f32> {
1219 let mut vector: Vec<f32> = vec![0.; 11];
1220 vector[0] = if is_harry_potter { 1. } else { 0. };
1221 vector[id + 1] = 1.;
1222 vector
1223 }
1224
1225 pub(crate) async fn setup_test_index(client: &Client, index: &Index) -> Result<(), Error> {
1226 let t0 = index.add_documents(&[
1227 Document { id: 0, kind: "text".into(), number: 0, value: S("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), nested: Nested { child: S("first") }, _vectors: Some(Vectors::from(vectorize(false, 0))) },
1228 Document { id: 1, kind: "text".into(), number: 10, value: S("dolor sit amet, consectetur adipiscing elit"), nested: Nested { child: S("second") }, _vectors: Some(Vectors::from(vectorize(false, 1))) },
1229 Document { id: 2, kind: "title".into(), number: 20, value: S("The Social Network"), nested: Nested { child: S("third") }, _vectors: Some(Vectors::from(vectorize(false, 2))) },
1230 Document { id: 3, kind: "title".into(), number: 30, value: S("Harry Potter and the Sorcerer's Stone"), nested: Nested { child: S("fourth") }, _vectors: Some(Vectors::from(vectorize(true, 3))) },
1231 Document { id: 4, kind: "title".into(), number: 40, value: S("Harry Potter and the Chamber of Secrets"), nested: Nested { child: S("fift") }, _vectors: Some(Vectors::from(vectorize(true, 4))) },
1232 Document { id: 5, kind: "title".into(), number: 50, value: S("Harry Potter and the Prisoner of Azkaban"), nested: Nested { child: S("sixth") }, _vectors: Some(Vectors::from(vectorize(true, 5))) },
1233 Document { id: 6, kind: "title".into(), number: 60, value: S("Harry Potter and the Goblet of Fire"), nested: Nested { child: S("seventh") }, _vectors: Some(Vectors::from(vectorize(true, 6))) },
1234 Document { id: 7, kind: "title".into(), number: 70, value: S("Harry Potter and the Order of the Phoenix"), nested: Nested { child: S("eighth") }, _vectors: Some(Vectors::from(vectorize(true, 7))) },
1235 Document { id: 8, kind: "title".into(), number: 80, value: S("Harry Potter and the Half-Blood Prince"), nested: Nested { child: S("ninth") }, _vectors: Some(Vectors::from(vectorize(true, 8))) },
1236 Document { id: 9, kind: "title".into(), number: 90, value: S("Harry Potter and the Deathly Hallows"), nested: Nested { child: S("tenth") }, _vectors: Some(Vectors::from(vectorize(true, 9))) },
1237 ], None).await?;
1238 let t1 = index
1239 .set_filterable_attributes(["kind", "value", "number"])
1240 .await?;
1241 let t2 = index.set_sortable_attributes(["title"]).await?;
1242
1243 t2.wait_for_completion(client, None, None).await?;
1244 t1.wait_for_completion(client, None, None).await?;
1245 t0.wait_for_completion(client, None, None).await?;
1246
1247 Ok(())
1248 }
1249
1250 #[derive(Debug, Serialize, Deserialize, PartialEq)]
1251 struct VideoDocument {
1252 id: usize,
1253 title: String,
1254 description: Option<String>,
1255 duration: u32,
1256 }
1257
1258 async fn setup_test_video_index(client: &Client, index: &Index) -> Result<(), Error> {
1259 let t0 = index
1260 .add_documents(
1261 &[
1262 VideoDocument {
1263 id: 0,
1264 title: S("Spring"),
1265 description: Some(S("A Blender Open movie")),
1266 duration: 123,
1267 },
1268 VideoDocument {
1269 id: 1,
1270 title: S("Wing It!"),
1271 description: None,
1272 duration: 234,
1273 },
1274 VideoDocument {
1275 id: 2,
1276 title: S("Coffee Run"),
1277 description: Some(S("Directed by Hjalti Hjalmarsson")),
1278 duration: 345,
1279 },
1280 VideoDocument {
1281 id: 3,
1282 title: S("Harry Potter and the Deathly Hallows"),
1283 description: None,
1284 duration: 7654,
1285 },
1286 ],
1287 None,
1288 )
1289 .await?;
1290 let t1 = index.set_filterable_attributes(["duration"]).await?;
1291 let t2 = index.set_sortable_attributes(["title"]).await?;
1292
1293 t2.wait_for_completion(client, None, None).await?;
1294 t1.wait_for_completion(client, None, None).await?;
1295 t0.wait_for_completion(client, None, None).await?;
1296 Ok(())
1297 }
1298
1299 pub(crate) async fn setup_embedder(client: &Client, index: &Index) -> Result<(), Error> {
1300 use crate::settings::Embedder;
1301 let embedder_setting = Embedder {
1302 source: EmbedderSource::UserProvided,
1303 dimensions: Some(11),
1304 ..Embedder::default()
1305 };
1306 index
1307 .set_settings(&crate::settings::Settings {
1308 embedders: Some(HashMap::from([("default".to_string(), embedder_setting)])),
1309 ..crate::settings::Settings::default()
1310 })
1311 .await?
1312 .wait_for_completion(client, None, None)
1313 .await?;
1314 Ok(())
1315 }
1316
1317 #[meilisearch_test]
1318 async fn test_multi_search(client: Client, index: Index) -> Result<(), Error> {
1319 setup_test_index(&client, &index).await?;
1320 let search_query_1 = SearchQuery::new(&index)
1321 .with_query("Sorcerer's Stone")
1322 .build();
1323 let search_query_2 = SearchQuery::new(&index)
1324 .with_query("Chamber of Secrets")
1325 .build();
1326
1327 let response = client
1328 .multi_search()
1329 .with_search_query(search_query_1)
1330 .with_search_query(search_query_2)
1331 .execute::<Document>()
1332 .await
1333 .unwrap();
1334
1335 assert_eq!(response.results.len(), 2);
1336 Ok(())
1337 }
1338
1339 #[meilisearch_test]
1340 async fn test_federated_multi_search(
1341 client: Client,
1342 test_index: Index,
1343 video_index: Index,
1344 ) -> Result<(), Error> {
1345 setup_test_index(&client, &test_index).await?;
1346 setup_test_video_index(&client, &video_index).await?;
1347
1348 let query_test_index = SearchQuery::new(&test_index).with_query("death").build();
1349 let query_video_index = SearchQuery::new(&video_index).with_query("death").build();
1350
1351 #[derive(Debug, Serialize, Deserialize, PartialEq)]
1352 #[serde(untagged)]
1353 enum AnyDocument {
1354 Document(Document),
1355 VideoDocument(VideoDocument),
1356 }
1357
1358 let mut multi_query = client.multi_search();
1360 multi_query.with_search_query_and_weight(query_test_index.clone(), 999.0);
1361 multi_query.with_search_query(query_video_index.clone());
1362 let response = multi_query
1363 .with_federation(FederationOptions::default())
1364 .execute::<AnyDocument>()
1365 .await?;
1366 assert_eq!(response.hits.len(), 2);
1367 assert_eq!(
1368 response.hits[0].result,
1369 AnyDocument::Document(Document {
1370 id: 9,
1371 kind: "title".into(),
1372 number: 90,
1373 value: S("Harry Potter and the Deathly Hallows"),
1374 nested: Nested { child: S("tenth") },
1375 _vectors: None,
1376 })
1377 );
1378 assert_eq!(
1379 response.hits[1].result,
1380 AnyDocument::VideoDocument(VideoDocument {
1381 id: 3,
1382 title: S("Harry Potter and the Deathly Hallows"),
1383 description: None,
1384 duration: 7654,
1385 })
1386 );
1387
1388 let mut multi_query = client.multi_search();
1390 multi_query.with_search_query(query_test_index.clone());
1391 multi_query.with_search_query_and_weight(query_video_index.clone(), 999.0);
1392 let response = multi_query
1393 .with_federation(FederationOptions::default())
1394 .execute::<AnyDocument>()
1395 .await?;
1396 assert_eq!(response.hits.len(), 2);
1397 assert_eq!(
1398 response.hits[0].result,
1399 AnyDocument::VideoDocument(VideoDocument {
1400 id: 3,
1401 title: S("Harry Potter and the Deathly Hallows"),
1402 description: None,
1403 duration: 7654,
1404 })
1405 );
1406 assert_eq!(
1407 response.hits[1].result,
1408 AnyDocument::Document(Document {
1409 id: 9,
1410 kind: "title".into(),
1411 number: 90,
1412 value: S("Harry Potter and the Deathly Hallows"),
1413 nested: Nested { child: S("tenth") },
1414 _vectors: None,
1415 })
1416 );
1417
1418 let mut multi_query = client.multi_search();
1420 multi_query.with_search_query(query_test_index.clone());
1421 multi_query.with_search_query(query_video_index.clone());
1422 let response = multi_query
1423 .with_federation(FederationOptions {
1424 limit: Some(1),
1425 ..Default::default()
1426 })
1427 .execute::<AnyDocument>()
1428 .await?;
1429
1430 assert_eq!(response.hits.len(), 1);
1431
1432 Ok(())
1433 }
1434
1435 #[meilisearch_test]
1436 async fn test_query_builder(_client: Client, index: Index) -> Result<(), Error> {
1437 let mut query = SearchQuery::new(&index);
1438 query.with_query("space").with_offset(42).with_limit(21);
1439
1440 let res = query.execute::<Document>().await.unwrap();
1441
1442 assert_eq!(res.query, S("space"));
1443 assert_eq!(res.limit, Some(21));
1444 assert_eq!(res.offset, Some(42));
1445 assert_eq!(res.estimated_total_hits, Some(0));
1446 Ok(())
1447 }
1448
1449 #[meilisearch_test]
1450 async fn test_query_numbered_pagination(client: Client, index: Index) -> Result<(), Error> {
1451 setup_test_index(&client, &index).await?;
1452
1453 let mut query = SearchQuery::new(&index);
1454 query.with_query("").with_page(2).with_hits_per_page(2);
1455
1456 let res = query.execute::<Document>().await.unwrap();
1457
1458 assert_eq!(res.page, Some(2));
1459 assert_eq!(res.hits_per_page, Some(2));
1460 assert_eq!(res.total_hits, Some(10));
1461 assert_eq!(res.total_pages, Some(5));
1462 Ok(())
1463 }
1464
1465 #[meilisearch_test]
1466 async fn test_query_string(client: Client, index: Index) -> Result<(), Error> {
1467 setup_test_index(&client, &index).await?;
1468
1469 let results: SearchResults<Document> = index.search().with_query("dolor").execute().await?;
1470 assert_eq!(results.hits.len(), 2);
1471 Ok(())
1472 }
1473
1474 #[meilisearch_test]
1475 async fn test_query_string_on_nested_field(client: Client, index: Index) -> Result<(), Error> {
1476 setup_test_index(&client, &index).await?;
1477
1478 let results: SearchResults<Document> =
1479 index.search().with_query("second").execute().await?;
1480
1481 assert_eq!(
1482 &Document {
1483 id: 1,
1484 value: S("dolor sit amet, consectetur adipiscing elit"),
1485 kind: S("text"),
1486 number: 10,
1487 nested: Nested { child: S("second") },
1488 _vectors: None,
1489 },
1490 &results.hits[0].result
1491 );
1492
1493 Ok(())
1494 }
1495
1496 #[meilisearch_test]
1497 async fn test_query_limit(client: Client, index: Index) -> Result<(), Error> {
1498 setup_test_index(&client, &index).await?;
1499
1500 let results: SearchResults<Document> = index.search().with_limit(5).execute().await?;
1501 assert_eq!(results.hits.len(), 5);
1502 Ok(())
1503 }
1504
1505 #[meilisearch_test]
1506 async fn test_query_page(client: Client, index: Index) -> Result<(), Error> {
1507 setup_test_index(&client, &index).await?;
1508
1509 let results: SearchResults<Document> = index.search().with_page(2).execute().await?;
1510 assert_eq!(results.page, Some(2));
1511 assert_eq!(results.hits_per_page, Some(20));
1512 Ok(())
1513 }
1514
1515 #[meilisearch_test]
1516 async fn test_query_hits_per_page(client: Client, index: Index) -> Result<(), Error> {
1517 setup_test_index(&client, &index).await?;
1518
1519 let results: SearchResults<Document> =
1520 index.search().with_hits_per_page(2).execute().await?;
1521 assert_eq!(results.page, Some(1));
1522 assert_eq!(results.hits_per_page, Some(2));
1523 Ok(())
1524 }
1525
1526 #[meilisearch_test]
1527 async fn test_query_offset(client: Client, index: Index) -> Result<(), Error> {
1528 setup_test_index(&client, &index).await?;
1529
1530 let results: SearchResults<Document> = index.search().with_offset(6).execute().await?;
1531 assert_eq!(results.hits.len(), 4);
1532 Ok(())
1533 }
1534
1535 #[meilisearch_test]
1536 async fn test_query_filter(client: Client, index: Index) -> Result<(), Error> {
1537 setup_test_index(&client, &index).await?;
1538
1539 let results: SearchResults<Document> = index
1540 .search()
1541 .with_filter("value = \"The Social Network\"")
1542 .execute()
1543 .await?;
1544 assert_eq!(results.hits.len(), 1);
1545
1546 let results: SearchResults<Document> = index
1547 .search()
1548 .with_filter("NOT value = \"The Social Network\"")
1549 .execute()
1550 .await?;
1551 assert_eq!(results.hits.len(), 9);
1552 Ok(())
1553 }
1554
1555 #[meilisearch_test]
1556 async fn test_query_filter_with_array(client: Client, index: Index) -> Result<(), Error> {
1557 setup_test_index(&client, &index).await?;
1558
1559 let results: SearchResults<Document> = index
1560 .search()
1561 .with_array_filter(vec![
1562 "value = \"The Social Network\"",
1563 "value = \"The Social Network\"",
1564 ])
1565 .execute()
1566 .await?;
1567 assert_eq!(results.hits.len(), 1);
1568
1569 Ok(())
1570 }
1571
1572 #[meilisearch_test]
1573 async fn test_query_facet_distribution(client: Client, index: Index) -> Result<(), Error> {
1574 setup_test_index(&client, &index).await?;
1575
1576 let mut query = SearchQuery::new(&index);
1577 query.with_facets(Selectors::All);
1578 let results: SearchResults<Document> = index.execute_query(&query).await?;
1579 assert_eq!(
1580 results
1581 .facet_distribution
1582 .unwrap()
1583 .get("kind")
1584 .unwrap()
1585 .get("title")
1586 .unwrap(),
1587 &8
1588 );
1589
1590 let mut query = SearchQuery::new(&index);
1591 query.with_facets(Selectors::Some(&["kind"]));
1592 let results: SearchResults<Document> = index.execute_query(&query).await?;
1593 assert_eq!(
1594 results
1595 .facet_distribution
1596 .clone()
1597 .unwrap()
1598 .get("kind")
1599 .unwrap()
1600 .get("title")
1601 .unwrap(),
1602 &8
1603 );
1604 assert_eq!(
1605 results
1606 .facet_distribution
1607 .unwrap()
1608 .get("kind")
1609 .unwrap()
1610 .get("text")
1611 .unwrap(),
1612 &2
1613 );
1614 Ok(())
1615 }
1616
1617 #[meilisearch_test]
1618 async fn test_query_attributes_to_retrieve(client: Client, index: Index) -> Result<(), Error> {
1619 setup_test_index(&client, &index).await?;
1620
1621 let results: SearchResults<Document> = index
1622 .search()
1623 .with_attributes_to_retrieve(Selectors::All)
1624 .execute()
1625 .await?;
1626 assert_eq!(results.hits.len(), 10);
1627
1628 let mut query = SearchQuery::new(&index);
1629 query.with_attributes_to_retrieve(Selectors::Some(&["kind", "id"])); assert!(index.execute_query::<Document>(&query).await.is_err()); Ok(())
1632 }
1633
1634 #[meilisearch_test]
1635 async fn test_query_sort(client: Client, index: Index) -> Result<(), Error> {
1636 setup_test_index(&client, &index).await?;
1637
1638 let mut query = SearchQuery::new(&index);
1639 query.with_query("harry potter");
1640 query.with_sort(&["title:desc"]);
1641 let results: SearchResults<Document> = index.execute_query(&query).await?;
1642 assert_eq!(results.hits.len(), 7);
1643 Ok(())
1644 }
1645
1646 #[meilisearch_test]
1647 async fn test_query_attributes_to_crop(client: Client, index: Index) -> Result<(), Error> {
1648 setup_test_index(&client, &index).await?;
1649
1650 let mut query = SearchQuery::new(&index);
1651 query.with_query("lorem ipsum");
1652 query.with_attributes_to_crop(Selectors::All);
1653 let results: SearchResults<Document> = index.execute_query(&query).await?;
1654 assert_eq!(
1655 &Document {
1656 id: 0,
1657 value: S("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do…"),
1658 kind: S("text"),
1659 number: 0,
1660 nested: Nested { child: S("first") },
1661 _vectors: None,
1662 },
1663 results.hits[0].formatted_result.as_ref().unwrap()
1664 );
1665
1666 let mut query = SearchQuery::new(&index);
1667 query.with_query("lorem ipsum");
1668 query.with_attributes_to_crop(Selectors::Some(&[("value", Some(5)), ("kind", None)]));
1669 let results: SearchResults<Document> = index.execute_query(&query).await?;
1670 assert_eq!(
1671 &Document {
1672 id: 0,
1673 value: S("Lorem ipsum dolor sit amet…"),
1674 kind: S("text"),
1675 number: 0,
1676 nested: Nested { child: S("first") },
1677 _vectors: None,
1678 },
1679 results.hits[0].formatted_result.as_ref().unwrap()
1680 );
1681 Ok(())
1682 }
1683
1684 #[meilisearch_test]
1685 async fn test_query_crop_length(client: Client, index: Index) -> Result<(), Error> {
1686 setup_test_index(&client, &index).await?;
1687
1688 let mut query = SearchQuery::new(&index);
1689 query.with_query("lorem ipsum");
1690 query.with_attributes_to_crop(Selectors::All);
1691 query.with_crop_length(200);
1692 let results: SearchResults<Document> = index.execute_query(&query).await?;
1693 assert_eq!(&Document {
1694 id: 0,
1695 value: S("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."),
1696 kind: S("text"),
1697 number: 0,
1698 nested: Nested { child: S("first") },
1699 _vectors: None,
1700 },
1701 results.hits[0].formatted_result.as_ref().unwrap());
1702
1703 let mut query = SearchQuery::new(&index);
1704 query.with_query("lorem ipsum");
1705 query.with_attributes_to_crop(Selectors::All);
1706 query.with_crop_length(5);
1707 let results: SearchResults<Document> = index.execute_query(&query).await?;
1708 assert_eq!(
1709 &Document {
1710 id: 0,
1711 value: S("Lorem ipsum dolor sit amet…"),
1712 kind: S("text"),
1713 number: 0,
1714 nested: Nested { child: S("first") },
1715 _vectors: None,
1716 },
1717 results.hits[0].formatted_result.as_ref().unwrap()
1718 );
1719 Ok(())
1720 }
1721
1722 #[meilisearch_test]
1723 async fn test_query_customized_crop_marker(client: Client, index: Index) -> Result<(), Error> {
1724 setup_test_index(&client, &index).await?;
1725
1726 let mut query = SearchQuery::new(&index);
1727 query.with_query("sed do eiusmod");
1728 query.with_attributes_to_crop(Selectors::All);
1729 query.with_crop_length(6);
1730 query.with_crop_marker("(ꈍᴗꈍ)");
1731
1732 let results: SearchResults<Document> = index.execute_query(&query).await?;
1733
1734 assert_eq!(
1735 &Document {
1736 id: 0,
1737 value: S("(ꈍᴗꈍ)sed do eiusmod tempor incididunt ut(ꈍᴗꈍ)"),
1738 kind: S("text"),
1739 number: 0,
1740 nested: Nested { child: S("first") },
1741 _vectors: None,
1742 },
1743 results.hits[0].formatted_result.as_ref().unwrap()
1744 );
1745 Ok(())
1746 }
1747
1748 #[meilisearch_test]
1749 async fn test_query_customized_highlight_pre_tag(
1750 client: Client,
1751 index: Index,
1752 ) -> Result<(), Error> {
1753 setup_test_index(&client, &index).await?;
1754
1755 let mut query = SearchQuery::new(&index);
1756 query.with_query("Social");
1757 query.with_attributes_to_highlight(Selectors::All);
1758 query.with_highlight_pre_tag("(⊃。•́‿•̀。)⊃ ");
1759 query.with_highlight_post_tag(" ⊂(´• ω •`⊂)");
1760
1761 let results: SearchResults<Document> = index.execute_query(&query).await?;
1762 assert_eq!(
1763 &Document {
1764 id: 2,
1765 value: S("The (⊃。•́‿•̀。)⊃ Social ⊂(´• ω •`⊂) Network"),
1766 kind: S("title"),
1767 number: 20,
1768 nested: Nested { child: S("third") },
1769 _vectors: None,
1770 },
1771 results.hits[0].formatted_result.as_ref().unwrap()
1772 );
1773
1774 Ok(())
1775 }
1776
1777 #[meilisearch_test]
1778 async fn test_query_attributes_to_highlight(client: Client, index: Index) -> Result<(), Error> {
1779 setup_test_index(&client, &index).await?;
1780
1781 let mut query = SearchQuery::new(&index);
1782 query.with_query("dolor text");
1783 query.with_attributes_to_highlight(Selectors::All);
1784 let results: SearchResults<Document> = index.execute_query(&query).await?;
1785 assert_eq!(
1786 &Document {
1787 id: 1,
1788 value: S("<em>dolor</em> sit amet, consectetur adipiscing elit"),
1789 kind: S("<em>text</em>"),
1790 number: 10,
1791 nested: Nested { child: S("second") },
1792 _vectors: None,
1793 },
1794 results.hits[0].formatted_result.as_ref().unwrap(),
1795 );
1796
1797 let mut query = SearchQuery::new(&index);
1798 query.with_query("dolor text");
1799 query.with_attributes_to_highlight(Selectors::Some(&["value"]));
1800 let results: SearchResults<Document> = index.execute_query(&query).await?;
1801 assert_eq!(
1802 &Document {
1803 id: 1,
1804 value: S("<em>dolor</em> sit amet, consectetur adipiscing elit"),
1805 kind: S("text"),
1806 number: 10,
1807 nested: Nested { child: S("second") },
1808 _vectors: None,
1809 },
1810 results.hits[0].formatted_result.as_ref().unwrap()
1811 );
1812 Ok(())
1813 }
1814
1815 #[meilisearch_test]
1816 async fn test_query_show_matches_position(client: Client, index: Index) -> Result<(), Error> {
1817 setup_test_index(&client, &index).await?;
1818
1819 let mut query = SearchQuery::new(&index);
1820 query.with_query("dolor text");
1821 query.with_show_matches_position(true);
1822 let results: SearchResults<Document> = index.execute_query(&query).await?;
1823 assert_eq!(results.hits[0].matches_position.as_ref().unwrap().len(), 2);
1824 assert_eq!(
1825 results.hits[0]
1826 .matches_position
1827 .as_ref()
1828 .unwrap()
1829 .get("value")
1830 .unwrap(),
1831 &vec![MatchRange {
1832 start: 0,
1833 length: 5,
1834 indices: None,
1835 }]
1836 );
1837 Ok(())
1838 }
1839
1840 #[meilisearch_test]
1841 async fn test_query_show_ranking_score(client: Client, index: Index) -> Result<(), Error> {
1842 setup_test_index(&client, &index).await?;
1843
1844 let mut query = SearchQuery::new(&index);
1845 query.with_query("dolor text");
1846 query.with_show_ranking_score(true);
1847 let results: SearchResults<Document> = index.execute_query(&query).await?;
1848 assert!(results.hits[0].ranking_score.is_some());
1849 Ok(())
1850 }
1851
1852 #[meilisearch_test]
1853 async fn test_query_show_ranking_score_details(
1854 client: Client,
1855 index: Index,
1856 ) -> Result<(), Error> {
1857 setup_test_index(&client, &index).await?;
1858
1859 let mut query = SearchQuery::new(&index);
1860 query.with_query("dolor text");
1861 query.with_show_ranking_score_details(true);
1862 let results: SearchResults<Document> = index.execute_query(&query).await.unwrap();
1863 assert!(results.hits[0].ranking_score_details.is_some());
1864 Ok(())
1865 }
1866
1867 #[meilisearch_test]
1868 async fn test_query_show_ranking_score_threshold(
1869 client: Client,
1870 index: Index,
1871 ) -> Result<(), Error> {
1872 setup_test_index(&client, &index).await?;
1873
1874 let mut query = SearchQuery::new(&index);
1875 query.with_query("dolor text");
1876 query.with_ranking_score_threshold(1.0);
1877 let results: SearchResults<Document> = index.execute_query(&query).await.unwrap();
1878 assert!(results.hits.is_empty());
1879 Ok(())
1880 }
1881
1882 #[meilisearch_test]
1883 async fn test_query_locales(client: Client, index: Index) -> Result<(), Error> {
1884 setup_test_index(&client, &index).await?;
1885
1886 let mut query = SearchQuery::new(&index);
1887 query.with_query("Harry Styles");
1888 query.with_locales(&["eng"]);
1889 let results: SearchResults<Document> = index.execute_query(&query).await.unwrap();
1890 assert_eq!(results.hits.len(), 7);
1891 Ok(())
1892 }
1893
1894 #[meilisearch_test]
1895 async fn test_phrase_search(client: Client, index: Index) -> Result<(), Error> {
1896 setup_test_index(&client, &index).await?;
1897
1898 let mut query = SearchQuery::new(&index);
1899 query.with_query("harry \"of Fire\"");
1900 let results: SearchResults<Document> = index.execute_query(&query).await?;
1901
1902 assert_eq!(results.hits.len(), 1);
1903 Ok(())
1904 }
1905
1906 #[meilisearch_test]
1907 async fn test_matching_strategy_all(client: Client, index: Index) -> Result<(), Error> {
1908 setup_test_index(&client, &index).await?;
1909
1910 let results = SearchQuery::new(&index)
1911 .with_query("Harry Styles")
1912 .with_matching_strategy(MatchingStrategies::ALL)
1913 .execute::<Document>()
1914 .await
1915 .unwrap();
1916
1917 assert_eq!(results.hits.len(), 0);
1918 Ok(())
1919 }
1920
1921 #[meilisearch_test]
1922 async fn test_matching_strategy_last(client: Client, index: Index) -> Result<(), Error> {
1923 setup_test_index(&client, &index).await?;
1924
1925 let results = SearchQuery::new(&index)
1926 .with_query("Harry Styles")
1927 .with_matching_strategy(MatchingStrategies::LAST)
1928 .execute::<Document>()
1929 .await
1930 .unwrap();
1931
1932 assert_eq!(results.hits.len(), 7);
1933 Ok(())
1934 }
1935
1936 #[meilisearch_test]
1937 async fn test_matching_strategy_frequency(client: Client, index: Index) -> Result<(), Error> {
1938 setup_test_index(&client, &index).await?;
1939
1940 let results = SearchQuery::new(&index)
1941 .with_query("Harry Styles")
1942 .with_matching_strategy(MatchingStrategies::FREQUENCY)
1943 .execute::<Document>()
1944 .await
1945 .unwrap();
1946
1947 assert_eq!(results.hits.len(), 7);
1948 Ok(())
1949 }
1950
1951 #[meilisearch_test]
1952 async fn test_distinct(client: Client, index: Index) -> Result<(), Error> {
1953 setup_test_index(&client, &index).await?;
1954
1955 let results = SearchQuery::new(&index)
1956 .with_distinct("kind")
1957 .execute::<Document>()
1958 .await
1959 .unwrap();
1960
1961 assert_eq!(results.hits.len(), 2);
1962 Ok(())
1963 }
1964
1965 #[meilisearch_test]
1966 async fn test_generate_tenant_token_from_client(
1967 client: Client,
1968 index: Index,
1969 ) -> Result<(), Error> {
1970 setup_test_index(&client, &index).await?;
1971
1972 let meilisearch_url = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1973 let key = KeyBuilder::new()
1974 .with_action(Action::All)
1975 .with_index("*")
1976 .execute(&client)
1977 .await
1978 .unwrap();
1979 let allowed_client = Client::new(meilisearch_url, Some(key.key)).unwrap();
1980
1981 let search_rules = vec![
1982 json!({ "*": {}}),
1983 json!({ "*": Value::Null }),
1984 json!(["*"]),
1985 json!({ "*": { "filter": "kind = text" } }),
1986 json!([index.uid.to_string()]),
1987 ];
1988
1989 for rules in search_rules {
1990 let token = allowed_client
1991 .generate_tenant_token(key.uid.clone(), rules, None, None)
1992 .expect("Cannot generate tenant token.");
1993
1994 let new_client = Client::new(meilisearch_url, Some(token.clone())).unwrap();
1995
1996 let result: SearchResults<Document> = new_client
1997 .index(index.uid.to_string())
1998 .search()
1999 .execute()
2000 .await?;
2001
2002 assert!(!result.hits.is_empty());
2003 }
2004
2005 Ok(())
2006 }
2007
2008 #[meilisearch_test]
2009 async fn test_facet_search_base(client: Client, index: Index) -> Result<(), Error> {
2010 setup_test_index(&client, &index).await?;
2011 let res = index.facet_search("kind").execute().await?;
2012 assert_eq!(res.facet_hits.len(), 2);
2013 Ok(())
2014 }
2015
2016 #[meilisearch_test]
2017 async fn test_facet_search_with_exhaustive_facet_count(
2018 client: Client,
2019 index: Index,
2020 ) -> Result<(), Error> {
2021 setup_test_index(&client, &index).await?;
2022 let res = index
2023 .facet_search("kind")
2024 .with_exhaustive_facet_count(true)
2025 .execute()
2026 .await?;
2027 assert_eq!(res.facet_hits.len(), 2);
2028 Ok(())
2029 }
2030
2031 #[meilisearch_test]
2032 async fn test_search_with_exhaustive_facet_count(
2033 client: Client,
2034 index: Index,
2035 ) -> Result<(), Error> {
2036 setup_test_index(&client, &index).await?;
2037
2038 let mut query = SearchQuery::new(&index);
2041 query
2042 .with_facets(Selectors::Some(&["kind"]))
2043 .with_exhaustive_facet_count(true);
2044
2045 let res = index.execute_query::<Document>(&query).await;
2046 match res {
2047 Ok(results) => {
2048 assert!(results.exhaustive_facet_count.is_some());
2049 Ok(())
2050 }
2051 Err(error)
2052 if matches!(
2053 error,
2054 Error::Meilisearch(MeilisearchError {
2055 error_code: ErrorCode::BadRequest,
2056 ..
2057 })
2058 ) =>
2059 {
2060 Ok(())
2062 }
2063 Err(e) => Err(e),
2064 }
2065 }
2066
2067 #[test]
2068 fn test_search_query_serialization_exhaustive_facet_count() {
2069 let client = Client::new(
2071 option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700"),
2072 Some(option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey")),
2073 )
2074 .unwrap();
2075 let index = client.index("dummy");
2076
2077 let mut query = SearchQuery::new(&index);
2078 query
2079 .with_facets(Selectors::Some(&["kind"]))
2080 .with_exhaustive_facet_count(true);
2081
2082 let v = serde_json::to_value(&query).unwrap();
2083 assert_eq!(
2084 v.get("exhaustiveFacetCount").and_then(|b| b.as_bool()),
2085 Some(true)
2086 );
2087 }
2088
2089 #[meilisearch_test]
2090 async fn test_facet_search_with_facet_query(client: Client, index: Index) -> Result<(), Error> {
2091 setup_test_index(&client, &index).await?;
2092 let res = index
2093 .facet_search("kind")
2094 .with_facet_query("title")
2095 .execute()
2096 .await?;
2097 assert_eq!(res.facet_hits.len(), 1);
2098 assert_eq!(res.facet_hits[0].value, "title");
2099 assert_eq!(res.facet_hits[0].count, 8);
2100 Ok(())
2101 }
2102
2103 #[meilisearch_test]
2104 async fn test_facet_search_with_attributes_to_search_on(
2105 client: Client,
2106 index: Index,
2107 ) -> Result<(), Error> {
2108 setup_test_index(&client, &index).await?;
2109 let res = index
2110 .facet_search("kind")
2111 .with_search_query("title")
2112 .with_attributes_to_search_on(&["value"])
2113 .execute()
2114 .await?;
2115 println!("{:?}", res);
2116 assert_eq!(res.facet_hits.len(), 0);
2117
2118 let res = index
2119 .facet_search("kind")
2120 .with_search_query("title")
2121 .with_attributes_to_search_on(&["kind"])
2122 .execute()
2123 .await?;
2124 assert_eq!(res.facet_hits.len(), 1);
2125 Ok(())
2126 }
2127
2128 #[meilisearch_test]
2129 async fn test_with_vectors(client: Client, index: Index) -> Result<(), Error> {
2130 setup_embedder(&client, &index).await?;
2131 setup_test_index(&client, &index).await?;
2132
2133 let results: SearchResults<Document> = index
2134 .search()
2135 .with_query("lorem ipsum")
2136 .with_retrieve_vectors(true)
2137 .execute()
2138 .await?;
2139 assert_eq!(results.hits.len(), 1);
2140 let expected = Some(Vectors::from(vectorize(false, 0)));
2141 assert_eq!(results.hits[0].result._vectors, expected);
2142
2143 let results: SearchResults<Document> = index
2144 .search()
2145 .with_query("lorem ipsum")
2146 .with_retrieve_vectors(false)
2147 .execute()
2148 .await?;
2149 assert_eq!(results.hits.len(), 1);
2150 assert_eq!(results.hits[0].result._vectors, None);
2151 Ok(())
2152 }
2153
2154 #[meilisearch_test]
2155 async fn test_query_vector_in_response(client: Client, index: Index) -> Result<(), Error> {
2156 setup_embedder(&client, &index).await?;
2157 setup_test_index(&client, &index).await?;
2158
2159 let mut query = SearchQuery::new(&index);
2160 let qv = vectorize(false, 0);
2161 query
2162 .with_hybrid("default", 1.0)
2163 .with_vector(&qv)
2164 .with_retrieve_vectors(true);
2165
2166 let results: SearchResults<Document> = index.execute_query(&query).await?;
2167
2168 if std::env::var("MSDK_DEBUG_RAW_SEARCH").ok().as_deref() == Some("1")
2169 && results.query_vector.is_none()
2170 {
2171 use crate::request::Method;
2172 let url = format!("{}/indexes/{}/search", index.client.get_host(), index.uid);
2173 let raw: serde_json::Value = index
2174 .client
2175 .http_client
2176 .request::<(), &SearchQuery<_>, serde_json::Value>(
2177 &url,
2178 Method::Post {
2179 body: &query,
2180 query: (),
2181 },
2182 200,
2183 )
2184 .await
2185 .unwrap();
2186 eprintln!("DEBUG raw search response: {}", raw);
2187 }
2188
2189 assert!(results.query_vector.is_some());
2190 assert_eq!(results.query_vector.as_ref().unwrap().len(), 11);
2191 Ok(())
2192 }
2193
2194 #[meilisearch_test]
2195 async fn test_hybrid(client: Client, index: Index) -> Result<(), Error> {
2196 setup_embedder(&client, &index).await?;
2197 setup_test_index(&client, &index).await?;
2198
2199 let results: SearchResults<Document> = index
2202 .search()
2203 .with_hybrid("default", 1.0)
2204 .with_vector(&vectorize(true, 0))
2205 .execute()
2206 .await?;
2207 let ids = results
2208 .hits
2209 .iter()
2210 .map(|hit| hit.result.id)
2211 .collect::<Vec<_>>();
2212 assert_eq!(ids, vec![0, 3, 4, 5, 6, 7, 8, 9, 1, 2]);
2213
2214 Ok(())
2215 }
2216
2217 #[meilisearch_test]
2218 async fn test_facet_search_with_search_query(
2219 client: Client,
2220 index: Index,
2221 ) -> Result<(), Error> {
2222 setup_test_index(&client, &index).await?;
2223 let res = index
2224 .facet_search("kind")
2225 .with_search_query("Harry Potter")
2226 .execute()
2227 .await?;
2228 assert_eq!(res.facet_hits.len(), 1);
2229 assert_eq!(res.facet_hits[0].value, "title");
2230 assert_eq!(res.facet_hits[0].count, 7);
2231 Ok(())
2232 }
2233
2234 #[meilisearch_test]
2235 async fn test_facet_search_with_filter(client: Client, index: Index) -> Result<(), Error> {
2236 setup_test_index(&client, &index).await?;
2237 let res = index
2238 .facet_search("kind")
2239 .with_filter("value = \"The Social Network\"")
2240 .execute()
2241 .await?;
2242 assert_eq!(res.facet_hits.len(), 1);
2243 assert_eq!(res.facet_hits[0].value, "title");
2244 assert_eq!(res.facet_hits[0].count, 1);
2245
2246 let res = index
2247 .facet_search("kind")
2248 .with_filter("NOT value = \"The Social Network\"")
2249 .execute()
2250 .await?;
2251 assert_eq!(res.facet_hits.len(), 2);
2252 Ok(())
2253 }
2254
2255 #[meilisearch_test]
2256 async fn test_facet_search_with_array_filter(
2257 client: Client,
2258 index: Index,
2259 ) -> Result<(), Error> {
2260 setup_test_index(&client, &index).await?;
2261 let res = index
2262 .facet_search("kind")
2263 .with_array_filter(vec![
2264 "value = \"The Social Network\"",
2265 "value = \"The Social Network\"",
2266 ])
2267 .execute()
2268 .await?;
2269 assert_eq!(res.facet_hits.len(), 1);
2270 assert_eq!(res.facet_hits[0].value, "title");
2271 assert_eq!(res.facet_hits[0].count, 1);
2272 Ok(())
2273 }
2274
2275 #[meilisearch_test]
2276 async fn test_facet_search_with_matching_strategy_all(
2277 client: Client,
2278 index: Index,
2279 ) -> Result<(), Error> {
2280 setup_test_index(&client, &index).await?;
2281 let res = index
2282 .facet_search("kind")
2283 .with_search_query("Harry Styles")
2284 .with_matching_strategy(MatchingStrategies::ALL)
2285 .execute()
2286 .await?;
2287 assert_eq!(res.facet_hits.len(), 0);
2288 Ok(())
2289 }
2290
2291 #[meilisearch_test]
2292 async fn test_facet_search_with_matching_strategy_last(
2293 client: Client,
2294 index: Index,
2295 ) -> Result<(), Error> {
2296 setup_test_index(&client, &index).await?;
2297 let res = index
2298 .facet_search("kind")
2299 .with_search_query("Harry Styles")
2300 .with_matching_strategy(MatchingStrategies::LAST)
2301 .execute()
2302 .await?;
2303 assert_eq!(res.facet_hits.len(), 1);
2304 assert_eq!(res.facet_hits[0].value, "title");
2305 assert_eq!(res.facet_hits[0].count, 7);
2306 Ok(())
2307 }
2308}