Skip to content

Commit 032deec

Browse files
Merge #792
792: Support facet distribution for federated search r=curquiza a=Barabasbalazs # Pull Request ## Related issue Fixes #787 ## What does this PR do? - Adds support for `federation.facetsByIndex` in the `POST /multi-search` route. - Allows for merging of returned facets with `federation.mergeFacets` in the `POST /multi-search` route ## 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! Co-authored-by: Balazs Barabas <[email protected]>
2 parents 031d9db + bd9a723 commit 032deec

File tree

8 files changed

+218
-20
lines changed

8 files changed

+218
-20
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.meilisearch.sdk;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Getter;
5+
import lombok.NoArgsConstructor;
6+
import lombok.Setter;
7+
8+
@Getter
9+
@Setter
10+
@AllArgsConstructor
11+
@NoArgsConstructor
12+
public class MergeFacets {
13+
private Integer maxValuesPerFacet;
14+
}

src/main/java/com/meilisearch/sdk/MultiSearchFederation.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
package com.meilisearch.sdk;
22

3+
import java.util.Map;
4+
import lombok.Getter;
35
import org.json.JSONObject;
46

7+
@Getter
58
public class MultiSearchFederation {
69

710
private Integer limit;
811
private Integer offset;
12+
private MergeFacets mergeFacets;
13+
private Map<String, String[]> facetsByIndex;
914

1015
public MultiSearchFederation setLimit(Integer limit) {
1116
this.limit = limit;
@@ -17,12 +22,14 @@ public MultiSearchFederation setOffset(Integer offset) {
1722
return this;
1823
}
1924

20-
public Integer getLimit() {
21-
return this.limit;
25+
public MultiSearchFederation setMergeFacets(MergeFacets mergeFacets) {
26+
this.mergeFacets = mergeFacets;
27+
return this;
2228
}
2329

24-
public Integer getOffset() {
25-
return this.offset;
30+
public MultiSearchFederation setFacetsByIndex(Map<String, String[]> facetsByIndex) {
31+
this.facetsByIndex = facetsByIndex;
32+
return this;
2633
}
2734

2835
/**

src/main/java/com/meilisearch/sdk/MultiSearchRequest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.meilisearch.sdk;
22

33
import java.util.ArrayList;
4-
import lombok.*;
54

65
public class MultiSearchRequest {
76
private ArrayList<IndexSearchRequest> queries;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.meilisearch.sdk.model;
2+
3+
import java.util.HashMap;
4+
import lombok.Getter;
5+
6+
@Getter
7+
public class FacetsByIndexInfo {
8+
private HashMap<String, HashMap<String, Integer>> distribution;
9+
private HashMap<String, FacetRating> stats;
10+
}

src/main/java/com/meilisearch/sdk/model/MultiSearchResult.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@
1515
public class MultiSearchResult implements Searchable {
1616
String indexUid;
1717
ArrayList<HashMap<String, Object>> hits;
18-
Object facetDistribution;
18+
HashMap<String, HashMap<String, Integer>> facetDistribution;
1919
HashMap<String, FacetRating> facetStats;
2020
int processingTimeMs;
2121
String query;
2222
int offset;
2323
int limit;
2424
int estimatedTotalHits;
25+
HashMap<String, FacetsByIndexInfo> facetsByIndex;
2526

2627
public MultiSearchResult() {}
2728
}

src/test/java/com/meilisearch/integration/SearchTest.java

Lines changed: 168 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.ArrayList;
2121
import java.util.HashMap;
2222
import java.util.HashSet;
23+
import java.util.Map;
2324
import org.junit.jupiter.api.AfterAll;
2425
import org.junit.jupiter.api.BeforeEach;
2526
import org.junit.jupiter.api.Tag;
@@ -86,8 +87,8 @@ public void testSearchOffset() throws Exception {
8687
SearchRequest searchRequest = SearchRequest.builder().q("a").offset(20).build();
8788
SearchResult searchResult = (SearchResult) index.search(searchRequest);
8889

89-
assertThat(searchResult.getHits(), hasSize(10));
90-
assertThat(searchResult.getEstimatedTotalHits(), is(equalTo(30)));
90+
assertThat(searchResult.getHits(), hasSize(11));
91+
assertThat(searchResult.getEstimatedTotalHits(), is(equalTo(31)));
9192
}
9293

9394
/** Test search limit */
@@ -105,7 +106,7 @@ public void testSearchLimit() throws Exception {
105106
SearchResult searchResult = (SearchResult) index.search(searchRequest);
106107

107108
assertThat(searchResult.getHits(), hasSize(2));
108-
assertThat(searchResult.getEstimatedTotalHits(), is(equalTo(30)));
109+
assertThat(searchResult.getEstimatedTotalHits(), is(equalTo(31)));
109110
}
110111

111112
/** Test search attributesToRetrieve */
@@ -310,7 +311,7 @@ public void testSearchWithMatchingStrategy() throws Exception {
310311
SearchResult searchResult = (SearchResult) index.search(searchRequest);
311312

312313
assertThat(searchResult.getHits(), hasSize(20));
313-
assertThat(searchResult.getEstimatedTotalHits(), is(equalTo(21)));
314+
assertThat(searchResult.getEstimatedTotalHits(), is(equalTo(22)));
314315
}
315316

316317
/** Test search with frequency matching strategy */
@@ -685,12 +686,10 @@ public void testRawSearchSortWithPlaceHolder() throws Exception {
685686
Results resGson = jsonGson.decode(index.rawSearch(searchRequest), Results.class);
686687

687688
assertThat(resGson.hits, is(arrayWithSize(20)));
688-
assertThat(resGson.hits[0].getId(), is(equalTo("155")));
689-
assertThat(resGson.hits[0].getTitle(), is(equalTo("The Dark Knight")));
690-
assertThat(resGson.hits[1].getId(), is(equalTo("671")));
691-
assertThat(
692-
resGson.hits[1].getTitle(),
693-
is(equalTo("Harry Potter and the Philosopher's Stone")));
689+
assertThat(resGson.hits[0].getId(), is(equalTo("2")));
690+
assertThat(resGson.hits[0].getTitle(), is(equalTo("Hobbit")));
691+
assertThat(resGson.hits[1].getId(), is(equalTo("155")));
692+
assertThat(resGson.hits[1].getTitle(), is(equalTo("The Dark Knight")));
694693
}
695694

696695
/** Test search matches */
@@ -728,7 +727,7 @@ public void testSearchPage() throws Exception {
728727
assertThat(searchResult.getHits(), hasSize(20));
729728
assertThat(searchResult.getPage(), is(equalTo(1)));
730729
assertThat(searchResult.getHitsPerPage(), is(equalTo(20)));
731-
assertThat(searchResult.getTotalHits(), is(equalTo(30)));
730+
assertThat(searchResult.getTotalHits(), is(equalTo(31)));
732731
assertThat(searchResult.getTotalPages(), is(equalTo(2)));
733732
}
734733

@@ -749,8 +748,8 @@ public void testSearchPagination() throws Exception {
749748
assertThat(searchResult.getHits(), hasSize(2));
750749
assertThat(searchResult.getPage(), is(equalTo(2)));
751750
assertThat(searchResult.getHitsPerPage(), is(equalTo(2)));
752-
assertThat(searchResult.getTotalHits(), is(equalTo(30)));
753-
assertThat(searchResult.getTotalPages(), is(equalTo(15)));
751+
assertThat(searchResult.getTotalHits(), is(equalTo(31)));
752+
assertThat(searchResult.getTotalPages(), is(equalTo(16)));
754753
}
755754

756755
/** Test place holder search */
@@ -931,6 +930,162 @@ public void testMultiSearchWithDistinct() throws Exception {
931930
}
932931
}
933932

933+
@Test
934+
public void testMultiSearchWithFacetsByIndex() {
935+
HashSet<String> indexUids = new HashSet<String>();
936+
indexUids.add("movies");
937+
indexUids.add("nestedMovies");
938+
939+
for (String indexUid : indexUids) {
940+
941+
Index index = client.index(indexUid);
942+
943+
TestData<Movie> nestedTestData = this.getTestData(NESTED_MOVIES, Movie.class);
944+
TaskInfo task1 = index.addDocuments(nestedTestData.getRaw());
945+
946+
index.waitForTask(task1.getTaskUid());
947+
948+
Settings settings = new Settings();
949+
settings.setFilterableAttributes(new String[] {"id", "title"});
950+
settings.setSortableAttributes(new String[] {"id"});
951+
952+
index.waitForTask(index.updateSettings(settings).getTaskUid());
953+
954+
TestData<Movie> moviesTestData = this.getTestData(MOVIES_INDEX, Movie.class);
955+
TaskInfo task2 = index.addDocuments(moviesTestData.getRaw());
956+
957+
index.waitForTask(task2.getTaskUid());
958+
}
959+
960+
MultiSearchRequest search = new MultiSearchRequest();
961+
962+
for (String indexUid : indexUids) {
963+
search.addQuery(new IndexSearchRequest(indexUid).setQuery("Hobbit"));
964+
}
965+
966+
MultiSearchFederation federation = new MultiSearchFederation();
967+
federation.setLimit(20);
968+
federation.setOffset(0);
969+
Map<String, String[]> facetsByIndex = new HashMap<String, String[]>();
970+
facetsByIndex.put("nestedMovies", new String[] {"title"});
971+
facetsByIndex.put("movies", new String[] {"title", "id"});
972+
federation.setFacetsByIndex(facetsByIndex);
973+
974+
MultiSearchResult results = client.multiSearch(search, federation);
975+
976+
assertThat(results.getHits().size(), is(4));
977+
978+
HashMap<String, FacetRating> facetStats = results.getFacetStats();
979+
HashMap<String, HashMap<String, Integer>> facetDistribution =
980+
results.getFacetDistribution();
981+
982+
HashMap<String, FacetsByIndexInfo> facetsByIndexInfo = results.getFacetsByIndex();
983+
984+
assertThat(facetDistribution, is(nullValue()));
985+
assertThat(facetStats, is(nullValue()));
986+
assertThat(facetsByIndexInfo, is(not(nullValue())));
987+
988+
for (String indexUid : indexUids) {
989+
FacetsByIndexInfo indexInfo = facetsByIndexInfo.get(indexUid);
990+
assertThat(indexInfo.getDistribution(), is(not(nullValue())));
991+
assertThat(indexInfo.getStats(), is(not(nullValue())));
992+
}
993+
994+
HashMap<String, HashMap<String, Integer>> moviesIndexDistribution =
995+
facetsByIndexInfo.get("movies").getDistribution();
996+
997+
assertThat(moviesIndexDistribution.get("id"), is(not(nullValue())));
998+
assertThat(moviesIndexDistribution.get("id").get("2"), is(equalTo(1)));
999+
assertThat(moviesIndexDistribution.get("id").get("5"), is(equalTo(1)));
1000+
assertThat(moviesIndexDistribution.get("title"), is(not(nullValue())));
1001+
assertThat(moviesIndexDistribution.get("title").get("Hobbit"), is(equalTo(1)));
1002+
assertThat(moviesIndexDistribution.get("title").get("The Hobbit"), is(equalTo(1)));
1003+
1004+
HashMap<String, FacetRating> moviesFacetRating = facetsByIndexInfo.get("movies").getStats();
1005+
FacetRating idMoviesFacetRating = moviesFacetRating.get("id");
1006+
1007+
assertThat(idMoviesFacetRating, is(not(nullValue())));
1008+
assertThat(idMoviesFacetRating.getMin(), is(equalTo(2.0)));
1009+
assertThat(idMoviesFacetRating.getMax(), is(equalTo(5.0)));
1010+
1011+
HashMap<String, HashMap<String, Integer>> nestedMoviesIndexDistribution =
1012+
facetsByIndexInfo.get("nestedMovies").getDistribution();
1013+
1014+
assertThat(nestedMoviesIndexDistribution.get("title"), is(not(nullValue())));
1015+
assertThat(nestedMoviesIndexDistribution.get("title").get("Hobbit"), is(equalTo(1)));
1016+
assertThat(nestedMoviesIndexDistribution.get("title").get("The Hobbit"), is(equalTo(1)));
1017+
1018+
HashMap<String, FacetRating> nestedMoviesFacetRating =
1019+
facetsByIndexInfo.get("nestedMovies").getStats();
1020+
assertThat(nestedMoviesFacetRating.size(), is(equalTo((0))));
1021+
}
1022+
1023+
@Test
1024+
public void testMultiSearchWithMergeFacets() {
1025+
HashSet<String> indexUids = new HashSet<String>();
1026+
indexUids.add("movies");
1027+
indexUids.add("nestedMovies");
1028+
1029+
for (String indexUid : indexUids) {
1030+
1031+
Index index = client.index(indexUid);
1032+
1033+
TestData<Movie> nestedTestData = this.getTestData(NESTED_MOVIES, Movie.class);
1034+
TaskInfo task1 = index.addDocuments(nestedTestData.getRaw());
1035+
1036+
index.waitForTask(task1.getTaskUid());
1037+
1038+
Settings settings = new Settings();
1039+
settings.setFilterableAttributes(new String[] {"id", "title"});
1040+
settings.setSortableAttributes(new String[] {"id"});
1041+
1042+
index.waitForTask(index.updateSettings(settings).getTaskUid());
1043+
1044+
TestData<Movie> moviesTestData = this.getTestData(MOVIES_INDEX, Movie.class);
1045+
TaskInfo task2 = index.addDocuments(moviesTestData.getRaw());
1046+
1047+
index.waitForTask(task2.getTaskUid());
1048+
}
1049+
1050+
MultiSearchRequest search = new MultiSearchRequest();
1051+
1052+
for (String indexUid : indexUids) {
1053+
search.addQuery(new IndexSearchRequest(indexUid).setQuery("Hobbit"));
1054+
}
1055+
1056+
MultiSearchFederation federation = new MultiSearchFederation();
1057+
federation.setLimit(20);
1058+
federation.setOffset(0);
1059+
federation.setMergeFacets(new MergeFacets(10));
1060+
Map<String, String[]> facetsByIndex = new HashMap<String, String[]>();
1061+
facetsByIndex.put("nestedMovies", new String[] {"title"});
1062+
facetsByIndex.put("movies", new String[] {"title", "id"});
1063+
federation.setFacetsByIndex(facetsByIndex);
1064+
1065+
MultiSearchResult results = client.multiSearch(search, federation);
1066+
1067+
assertThat(results.getHits().size(), is(4));
1068+
1069+
HashMap<String, FacetRating> facetStats = results.getFacetStats();
1070+
HashMap<String, HashMap<String, Integer>> facetDistribution =
1071+
results.getFacetDistribution();
1072+
1073+
assertThat(facetDistribution, is(not(nullValue())));
1074+
assertThat(facetStats, is(not(nullValue())));
1075+
assertThat(results.getFacetsByIndex(), is(nullValue()));
1076+
1077+
FacetRating idFacet = facetStats.get("id");
1078+
1079+
assertThat(idFacet.getMin(), is(equalTo(2.0)));
1080+
assertThat(idFacet.getMax(), is(equalTo(5.0)));
1081+
1082+
assertThat(facetDistribution.get("id").get("2"), is(equalTo(1)));
1083+
assertThat(facetDistribution.get("id").get("5"), is(equalTo(1)));
1084+
1085+
assertThat(facetDistribution.get("title").get("Hobbit"), is(equalTo(2)));
1086+
assertThat(facetDistribution.get("title").get("The Hobbit"), is(equalTo(2)));
1087+
}
1088+
9341089
@Test
9351090
public void testSimilarDocuments() throws Exception {
9361091
HashMap<String, Boolean> features = new HashMap();

src/test/java/com/meilisearch/integration/TenantTokenTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public void testGenerateTenantTokenWithFilter() throws Exception {
108108

109109
assertThat(searchResult.getHits().size(), is(equalTo(20)));
110110
assertThat(searchResult.getLimit(), is(equalTo(20)));
111-
assertThat(searchResult.getEstimatedTotalHits(), is(equalTo(30)));
111+
assertThat(searchResult.getEstimatedTotalHits(), is(equalTo(31)));
112112
}
113113

114114
/** Test Create Tenant Token with expiration date */

src/test/resources/movies.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,5 +396,17 @@
396396
"Thriller",
397397
"Drama"
398398
]
399+
},
400+
{
401+
"id": 2,
402+
"title": "Hobbit",
403+
"poster": "https://www.imdb.com/title/tt0903624/mediaviewer/rm3577719808/?ref_=tt_ov_i",
404+
"overview": "A reluctant Hobbit, Bilbo Baggins, sets out to the Lonely Mountain with a spirited group of dwarves to reclaim their mountain home and the gold within it from the dragon Smaug.",
405+
"release_date": "2021-12-14",
406+
"language": "en",
407+
"genres": [
408+
"Adventure",
409+
"Fantasy"
410+
]
399411
}
400412
]

0 commit comments

Comments
 (0)