Skip to content

Commit 80f2a5b

Browse files
committed
feat(multi-search): add union search functionality and update docker-compose version
1 parent 3703f29 commit 80f2a5b

File tree

3 files changed

+132
-14
lines changed

3 files changed

+132
-14
lines changed

docker-compose.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
version: "3"
1+
version: '3'
22

33
services:
44
typesense:
5-
image: typesense/typesense:0.24.1
6-
container_name: "typesense"
5+
image: typesense/typesense:29.0
6+
container_name: 'typesense'
77
ports:
8-
- "8108:8108"
8+
- '8108:8108'
99
volumes:
1010
- data-dir:/data
1111
environment:
1212
TYPESENSE_DATA_DIR: /data
1313
TYPESENSE_API_KEY: xyz
14-
restart: "no"
14+
restart: 'no'
1515

1616
volumes:
17-
data-dir:
17+
data-dir:

src/main/java/org/typesense/api/MultiSearch.java

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.typesense.api;
22

33
import org.typesense.model.MultiSearchResult;
4+
import org.typesense.model.SearchResult;
45
import org.typesense.model.MultiSearchSearchesParameter;
56

67
import java.util.Map;
@@ -14,8 +15,84 @@ public MultiSearch(ApiCall apiCall) {
1415
this.apiCall = apiCall;
1516
}
1617

18+
/**
19+
* Performs a federated multi-search, returning individual result sets for each
20+
* query.
21+
* <p>
22+
* This method is used for running multiple search queries in a single API call,
23+
* where
24+
* the results for each query are returned as separate and distinct sets. This
25+
* is
26+
* also known as a
27+
* <a href=
28+
* "https://typesense.org/docs/latest/api/federated-multi-search.html#federated-search"
29+
* target="_blank">Federated Search</a>.
30+
* <p>
31+
* This method strictly handles non-union searches. It will validate that the
32+
* {@code union} flag is set to {@code false}. If the flag is {@code true}, it
33+
* will
34+
* throw an {@link IllegalArgumentException} to prevent unexpected behavior. For
35+
* union
36+
* searches, use the {@link #performUnion(MultiSearchSearchesParameter, Map)}
37+
* method instead.
38+
*
39+
* @param multiSearchParameters The object containing the list of search queries
40+
* to perform.
41+
* The {@code union} flag must be {@code false} or
42+
* unset.
43+
* @param common_params A map of common parameters that will be applied
44+
* to every
45+
* search query in the request. Can be null or
46+
* empty.
47+
* @return A {@link MultiSearchResult} object containing a list of individual
48+
* search
49+
* results. The order of results in this list is guaranteed to match the
50+
* order of the queries sent in the request.
51+
* @throws IllegalArgumentException if the {@code union} flag in
52+
* {@code multiSearchParameters}
53+
* is set to {@code true}, as this method is
54+
* strictly for
55+
* non-union federated searches.
56+
* @throws Exception if there is an issue with the API call, such
57+
* as a network
58+
* problem or an error response from the
59+
* server.
60+
*/
1761
public MultiSearchResult perform(MultiSearchSearchesParameter multiSearchParameters,
18-
Map<String, String> common_params) throws Exception {
19-
return this.apiCall.post(MultiSearch.RESOURCEPATH, multiSearchParameters, common_params, MultiSearchResult.class);
62+
Map<String, String> common_params) throws Exception {
63+
if (multiSearchParameters.isUnion()) {
64+
throw new IllegalArgumentException(
65+
"The 'perform()' method is for non-union searches. For a union search, please use the 'performUnion()' method.");
66+
}
67+
return this.apiCall.post(MultiSearch.RESOURCEPATH, multiSearchParameters, common_params,
68+
MultiSearchResult.class);
69+
}
70+
71+
/**
72+
* Performs a <a href=
73+
* "https://typesense.org/docs/latest/api/federated-multi-search.html#union-search"
74+
* target="_blank">Union Search</a>
75+
* and returns a single, combined search result.
76+
* <p>
77+
* This method offers a convenient way to perform a union search. It
78+
* automatically
79+
* enforces {@code union=true}, merging results from all queries into a single
80+
* ordered set of hits without requiring you to set the flag manually.
81+
* <p>
82+
* This method is guaranteed to not modify the provided
83+
* {@code multiSearchParameters}
84+
* object, making it safe to reuse the same parameter object across multiple API
85+
* calls.
86+
*
87+
*/
88+
public SearchResult performUnion(MultiSearchSearchesParameter multiSearchParameters,
89+
Map<String, String> common_params) throws Exception {
90+
// Create a shallow copy to safely enforce union=true without modifying the
91+
// caller's original parameters.
92+
MultiSearchSearchesParameter copiedParams = new MultiSearchSearchesParameter();
93+
copiedParams.setSearches(multiSearchParameters.getSearches());
94+
copiedParams.setUnion(true);
95+
96+
return this.apiCall.post(MultiSearch.RESOURCEPATH, copiedParams, common_params, SearchResult.class);
2097
}
2198
}

src/test/java/org/typesense/api/MultiSearchTest.java

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,18 @@
77
import org.typesense.model.Field;
88
import org.typesense.model.MultiSearchCollectionParameters;
99
import org.typesense.model.MultiSearchResult;
10+
import org.typesense.model.SearchResult;
1011
import org.typesense.model.MultiSearchSearchesParameter;
1112

1213
import java.util.ArrayList;
1314
import java.util.HashMap;
15+
import java.util.HashSet;
1416
import java.util.List;
1517
import java.util.Map;
18+
import java.util.Set;
1619

1720
import static org.junit.jupiter.api.Assertions.assertEquals;
21+
import static org.junit.jupiter.api.Assertions.assertTrue;
1822

1923
class MultiSearchTest {
2024

@@ -35,12 +39,23 @@ void setUp() throws Exception {
3539
CollectionSchema collectionSchema = new CollectionSchema();
3640
collectionSchema.name("embeddings").fields(fields);
3741
client.collections().create(collectionSchema);
42+
// create another collection for union search
43+
collectionSchema.name("embeddings-2").fields(fields);
44+
client.collections().create(collectionSchema);
45+
46+
float[] vecVals = { 0.12f, 0.45f, 0.87f, 0.18f };
47+
Map<String, Object> doc1 = new HashMap<>();
48+
doc1.put("title", "Romeo and Juliet");
49+
doc1.put("vec", vecVals);
50+
doc1.put("source", "embeddings_1");
51+
client.collections("embeddings").documents().create(doc1);
3852

39-
float[] vecVals = {0.12f, 0.45f, 0.87f, 0.18f};
40-
Map<String, Object> doc = new HashMap<>();
41-
doc.put("title", "Romeo and Juliet");
42-
doc.put("vec", vecVals);
43-
client.collections("embeddings").documents().create(doc);
53+
// Document for the second collection
54+
Map<String, Object> doc2 = new HashMap<>();
55+
doc2.put("title", "Romeo and Juliet from collection 2");
56+
doc2.put("vec", new float[] { 0.12f, 0.45f, 0.87f, 0.18f });
57+
doc2.put("source", "embeddings_2");
58+
client.collections("embeddings-2").documents().create(doc2);
4459
}
4560

4661
@AfterEach
@@ -55,10 +70,36 @@ void testSearch() throws Exception {
5570
search1.setQ("*");
5671
search1.setVectorQuery("vec:([0.96826,0.94,0.39557,0.306488], k:10)");
5772

58-
MultiSearchSearchesParameter multiSearchParameters = new MultiSearchSearchesParameter().addSearchesItem(search1);
73+
MultiSearchSearchesParameter multiSearchParameters = new MultiSearchSearchesParameter()
74+
.addSearchesItem(search1);
5975
MultiSearchResult response = this.client.multiSearch.perform(multiSearchParameters, null);
6076
assertEquals(1, response.getResults().size());
6177
assertEquals(1, response.getResults().get(0).getHits().size());
6278
assertEquals("0", response.getResults().get(0).getHits().get(0).getDocument().get("id"));
6379
}
80+
81+
@Test
82+
void testUnionSearch() throws Exception {
83+
MultiSearchCollectionParameters search1 = new MultiSearchCollectionParameters();
84+
search1.setCollection("embeddings");
85+
search1.setQ("*");
86+
87+
MultiSearchCollectionParameters search2 = new MultiSearchCollectionParameters();
88+
search2.setCollection("embeddings-2");
89+
search2.setQ("*");
90+
91+
MultiSearchSearchesParameter multiSearchParameters = new MultiSearchSearchesParameter()
92+
.addSearchesItem(search1).addSearchesItem(search2);
93+
SearchResult response = this.client.multiSearch.performUnion(multiSearchParameters, null);
94+
95+
assertEquals(2, response.getHits().size());
96+
assertEquals(2, response.getUnionRequestParams().size());
97+
98+
Set<String> sources = new HashSet<>();
99+
sources.add((String) response.getHits().get(0).getDocument().get("source"));
100+
sources.add((String) response.getHits().get(1).getDocument().get("source"));
101+
102+
assertTrue(sources.contains("embeddings_1"), "Results should contain a document from embeddings_1");
103+
assertTrue(sources.contains("embeddings_2"), "Results should contain a document from embeddings_2");
104+
}
64105
}

0 commit comments

Comments
 (0)