Skip to content
This repository was archived by the owner on Jan 31, 2022. It is now read-only.

Commit 6c03d9f

Browse files
author
Clément Le Provost
authored
Merge pull request #313 from algolia/feat/offline-facet-search
Support offline search for facet values
2 parents 9b14509 + c398e77 commit 6c03d9f

File tree

7 files changed

+242
-11
lines changed

7 files changed

+242
-11
lines changed

algoliasearch/build-offline.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ android {
4040
}
4141

4242
dependencies {
43-
offlineCompile "com.algolia:algoliasearch-offline-core-android:1.0.1"
43+
offlineCompile "com.algolia:algoliasearch-offline-core-android:1.1.0"
4444
}
4545

4646

algoliasearch/src/main/java/com/algolia/search/saas/Index.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ public Request searchForFacetValuesAsync(@NonNull String facetName, @NonNull Str
201201
* @param handler A Completion handler that will be notified of the request's outcome.
202202
* @return A cancellable request.
203203
*/
204-
public Request searchForFacetValues(@NonNull String facetName, @NonNull String text, @NonNull final CompletionHandler handler) throws AlgoliaException {
204+
public Request searchForFacetValues(@NonNull String facetName, @NonNull String text, @NonNull final CompletionHandler handler) {
205205
return searchForFacetValues(facetName, text, null, handler);
206206
}
207207

@@ -214,7 +214,7 @@ public Request searchForFacetValues(@NonNull String facetName, @NonNull String t
214214
* @param handler A Completion handler that will be notified of the request's outcome.
215215
* @return A cancellable request.
216216
*/
217-
public Request searchForFacetValuesAsync(@NonNull String facetName, @NonNull String facetText, @Nullable Query query, @NonNull final CompletionHandler handler) throws AlgoliaException {
217+
public Request searchForFacetValuesAsync(@NonNull String facetName, @NonNull String facetText, @Nullable Query query, @NonNull final CompletionHandler handler) {
218218
return searchForFacetValues(facetName, facetText, query, handler);
219219
}
220220

@@ -227,7 +227,7 @@ public Request searchForFacetValuesAsync(@NonNull String facetName, @NonNull Str
227227
* @param handler A Completion handler that will be notified of the request's outcome.
228228
* @return A cancellable request.
229229
*/
230-
public Request searchForFacetValues(@NonNull String facetName, @NonNull String facetText, @Nullable Query query, @NonNull final CompletionHandler handler) throws AlgoliaException {
230+
public Request searchForFacetValues(@NonNull String facetName, @NonNull String facetText, @Nullable Query query, @NonNull final CompletionHandler handler) {
231231
try {
232232
final String path = "/1/indexes/" + getEncodedIndexName() + "/facets/" + URLEncoder.encode(facetName, "UTF-8") + "/query";
233233
final Query params = (query != null ? new Query(query) : new Query());
@@ -243,7 +243,7 @@ protected JSONObject run() throws AlgoliaException {
243243
}
244244
}.start();
245245
} catch (UnsupportedEncodingException | JSONException e) {
246-
throw new AlgoliaException(e.getMessage());
246+
throw new RuntimeException(e); // should never happen
247247
}
248248
}
249249

algoliasearch/src/offline/java/com/algolia/search/saas/MirroredIndex.java

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1504,6 +1504,99 @@ private JSONObject _getObjectsOffline(@NonNull final List<String> objectIDs, fin
15041504
}
15051505
}
15061506

1507+
// ----------------------------------------------------------------------
1508+
// Search for facet values
1509+
// ----------------------------------------------------------------------
1510+
1511+
@Override
1512+
public Request searchForFacetValues(@NonNull String facetName, @NonNull String text, @Nullable Query query, @NonNull final CompletionHandler completionHandler) {
1513+
// A non-mirrored index behaves exactly as an online index.
1514+
if (!mirrored) {
1515+
return super.searchForFacetValues(facetName, text, query, completionHandler);
1516+
}
1517+
// A mirrored index launches a mixed offline/online request.
1518+
else {
1519+
final Query queryCopy = query != null ? new Query(query) : null;
1520+
return new MixedFacetSearchRequest(facetName, text, queryCopy, completionHandler).start();
1521+
}
1522+
}
1523+
1524+
/**
1525+
* Search for facet values, explicitly targeting the online API, not the offline mirror.
1526+
* Same parameters as {@link Index#searchForFacetValues(String, String, Query, CompletionHandler)}.
1527+
*/
1528+
public Request searchForFacetValuesOnline(@NonNull String facetName, @NonNull String text, @Nullable Query query, @NonNull final CompletionHandler completionHandler) {
1529+
return super.searchForFacetValues(facetName, text, query, new CompletionHandler() {
1530+
@Override
1531+
public void requestCompleted(JSONObject content, AlgoliaException error) {
1532+
try {
1533+
if (content != null)
1534+
content.put(JSON_KEY_ORIGIN, JSON_VALUE_ORIGIN_REMOTE);
1535+
}
1536+
catch (JSONException e) {
1537+
throw new RuntimeException(e); // should never happen
1538+
}
1539+
completionHandler.requestCompleted(content, error);
1540+
}
1541+
});
1542+
}
1543+
1544+
/**
1545+
* Search for facet values, explicitly targeting the offline mirror, not the online API.
1546+
*/
1547+
public Request searchForFacetValuesOffline(final @NonNull String facetName, final @NonNull String text, @Nullable Query query, @NonNull final CompletionHandler completionHandler) {
1548+
if (!mirrored) {
1549+
throw new IllegalStateException("Offline requests are only available when the index is mirrored");
1550+
}
1551+
final Query queryCopy = query != null ? new Query(query) : null;
1552+
return getClient().new AsyncTaskRequest(completionHandler, getClient().localSearchExecutorService) {
1553+
@NonNull
1554+
@Override
1555+
protected JSONObject run() throws AlgoliaException {
1556+
return _searchForFacetValuesOffline(facetName, text, queryCopy);
1557+
}
1558+
}.start();
1559+
}
1560+
1561+
private class MixedFacetSearchRequest extends OnlineOfflineRequest {
1562+
private final @NonNull String facetName;
1563+
private final @NonNull String facetQuery;
1564+
private final Query query;
1565+
1566+
public MixedFacetSearchRequest(@NonNull String facetName, @NonNull String facetQuery, @Nullable Query query, @NonNull CompletionHandler completionHandler) {
1567+
super(completionHandler);
1568+
this.facetName = facetName;
1569+
this.facetQuery = facetQuery;
1570+
this.query = query;
1571+
}
1572+
1573+
@Override
1574+
protected Request startOnlineRequest(CompletionHandler completionHandler) {
1575+
return searchForFacetValuesOnline(facetName, facetQuery, query, completionHandler);
1576+
}
1577+
1578+
@Override
1579+
protected Request startOfflineRequest(CompletionHandler completionHandler) {
1580+
return searchForFacetValuesOffline(facetName, facetQuery, query, completionHandler);
1581+
}
1582+
}
1583+
1584+
private JSONObject _searchForFacetValuesOffline(@NonNull String facetName, @NonNull String text, @Nullable Query query) throws AlgoliaException {
1585+
try {
1586+
Response searchResults = getLocalIndex().searchForFacetValues(facetName, text, query != null ? query.build() : null);
1587+
if (searchResults.getStatusCode() == 200) {
1588+
String jsonString = new String(searchResults.getData(), "UTF-8");
1589+
return new JSONObject(jsonString); // NOTE: Origin tagging performed by the SDK
1590+
}
1591+
else {
1592+
throw new AlgoliaException(searchResults.getErrorMessage(), searchResults.getStatusCode());
1593+
}
1594+
}
1595+
catch (JSONException | UnsupportedEncodingException e) {
1596+
throw new AlgoliaException("Search failed", e);
1597+
}
1598+
}
1599+
15071600
// ----------------------------------------------------------------------
15081601
// Listeners
15091602
// ----------------------------------------------------------------------

algoliasearch/src/offline/java/com/algolia/search/saas/OfflineIndex.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,28 @@ private JSONObject browseFromSync(@NonNull String cursor) throws AlgoliaExceptio
390390
return OfflineClient.parseSearchResults(localIndex.browse(query.build()));
391391
}
392392

393+
/**
394+
* Search for facet values (asynchronously).
395+
* Same parameters as {@link Index#searchForFacetValues(String, String, Query, CompletionHandler)}.
396+
*/
397+
public Request searchForFacetValuesAsync(final @NonNull String facetName, final @NonNull String facetQuery, @Nullable Query query, @NonNull CompletionHandler completionHandler) {
398+
final Query queryCopy = query != null ? new Query(query) : null;
399+
return getClient().new AsyncTaskRequest(completionHandler) {
400+
@NonNull
401+
@Override
402+
protected JSONObject run() throws AlgoliaException {
403+
return searchForFacetValuesSync(facetName, facetQuery, queryCopy);
404+
}
405+
}.start();
406+
}
407+
408+
/**
409+
* Search for facet values (synchronously).
410+
*/
411+
private JSONObject searchForFacetValuesSync(@NonNull String facetName, @NonNull String facetQuery, @Nullable Query query) throws AlgoliaException {
412+
return OfflineClient.parseSearchResults(localIndex.searchForFacetValues(facetName, facetQuery, query != null ? query.build() : null));
413+
}
414+
393415
// ----------------------------------------------------------------------
394416
// Transaction management
395417
// ----------------------------------------------------------------------

algoliasearch/src/testOffline/java/com/algolia/search/saas/MirroredIndexTest.java

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -121,18 +121,24 @@ public void doRequestCompleted(JSONObject content, AlgoliaException error) {
121121
@Override
122122
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
123123
assertNull(error);
124-
// Populate the online index.
125-
index.addObjectsAsync(new JSONArray(moreObjects.values()), new AssertCompletionHandler() {
124+
index.setSettingsAsync(settings, new AssertCompletionHandler() {
126125
@Override
127126
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
128127
assertNull(error);
129-
int taskID = content.optInt("taskID", -1);
130-
assertNotEquals(-1, taskID);
131-
index.waitTaskAsync(Integer.toString(taskID), new AssertCompletionHandler() {
128+
// Populate the online index.
129+
index.addObjectsAsync(new JSONArray(moreObjects.values()), new AssertCompletionHandler() {
132130
@Override
133131
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
134132
assertNull(error);
135-
completionHandler.syncCompleted(error);
133+
int taskID = content.optInt("taskID", -1);
134+
assertNotEquals(-1, taskID);
135+
index.waitTaskAsync(Integer.toString(taskID), new AssertCompletionHandler() {
136+
@Override
137+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
138+
assertNull(error);
139+
completionHandler.syncCompleted(error);
140+
}
141+
});
136142
}
137143
});
138144
}
@@ -707,4 +713,62 @@ public void doRequestCompleted(JSONObject content, AlgoliaException error) {
707713
}
708714
});
709715
}
716+
717+
@Test
718+
public void testSearchForFacetValues() throws Exception {
719+
final CountDownLatch signal = new CountDownLatch(3);
720+
721+
// Populate the online index & sync the offline mirror.
722+
final MirroredIndex index = client.getIndex(Helpers.safeIndexName(Helpers.getMethodName()));
723+
sync(index, new SyncCompletionHandler() {
724+
@Override
725+
public void syncCompleted(@Nullable Throwable error) {
726+
assertNull(error);
727+
728+
// Query the online index explicitly.
729+
index.searchForFacetValuesOnline("series", "", null, new AssertCompletionHandler() {
730+
@Override
731+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
732+
assertNull(error);
733+
JSONArray facetHits = content.optJSONArray("facetHits");
734+
assertNotNull(facetHits);
735+
assertEquals(2, facetHits.length());
736+
assertEquals("remote", content.optString("origin"));
737+
signal.countDown();
738+
739+
// Test offline fallback.
740+
client.setReadHosts("unknown.algolia.com");
741+
index.setRequestStrategy(MirroredIndex.Strategy.FALLBACK_ON_FAILURE);
742+
index.searchForFacetValues("series", "pea", new Query().setQuery("snoopy"), new AssertCompletionHandler() {
743+
@Override
744+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
745+
assertNull(error);
746+
JSONArray facetHits = content.optJSONArray("facetHits");
747+
assertNotNull(facetHits);
748+
assertEquals(1, facetHits.length());
749+
assertEquals("Peanuts", facetHits.optJSONObject(0).optString("value"));
750+
assertEquals(1, facetHits.optJSONObject(0).optInt("count"));
751+
assertEquals("local", content.optString("origin"));
752+
signal.countDown();
753+
}
754+
});
755+
}
756+
});
757+
758+
// Query the offline index explicitly.
759+
index.searchForFacetValuesOffline("series", "", null, new AssertCompletionHandler() {
760+
@Override
761+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
762+
assertNull(error);
763+
JSONArray facetHits = content.optJSONArray("facetHits");
764+
assertEquals(1, facetHits.length());
765+
assertEquals("Peanuts", facetHits.optJSONObject(0).optString("value"));
766+
assertEquals(3, facetHits.optJSONObject(0).optInt("count"));
767+
assertEquals("local", content.optString("origin"));
768+
signal.countDown();
769+
}
770+
});
771+
}
772+
});
773+
}
710774
}

algoliasearch/src/testOffline/java/com/algolia/search/saas/OfflineIndexTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,4 +563,40 @@ public void doRequestCompleted(JSONObject content, AlgoliaException error) {
563563
}
564564
});
565565
}
566+
567+
@Test
568+
public void testSearchForFacetValues() throws Exception {
569+
final CountDownLatch signal = new CountDownLatch(1);
570+
final OfflineIndex index = client.getOfflineIndex(Helpers.getMethodName());
571+
final OfflineIndex.WriteTransaction transaction = index.newTransaction();
572+
transaction.setSettingsAsync(settings, new AssertCompletionHandler() {
573+
@Override
574+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
575+
transaction.saveObjectsAsync(new JSONArray(objects.values()), new AssertCompletionHandler() {
576+
@Override
577+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
578+
assertNull(error);
579+
transaction.commitAsync(new AssertCompletionHandler() {
580+
@Override
581+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
582+
final Query query = new Query("snoopy");
583+
index.searchForFacetValuesAsync("series", "pea", new Query().setQuery("snoopy"), new AssertCompletionHandler() {
584+
@Override
585+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
586+
assertNotNull(content);
587+
JSONArray facetHits = content.optJSONArray("facetHits");
588+
assertNotNull(facetHits);
589+
assertEquals(1, facetHits.length());
590+
assertEquals("Peanuts", facetHits.optJSONObject(0).optString("value"));
591+
assertEquals(1, facetHits.optJSONObject(0).optInt("count"));
592+
signal.countDown();
593+
}
594+
});
595+
}
596+
});
597+
}
598+
});
599+
}
600+
});
601+
}
566602
}

algoliasearch/src/testOffline/java/com/algolia/search/saas/OfflineTestBase.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,22 @@ public abstract class OfflineTestBase extends RobolectricTestCase {
8787
}
8888
}
8989

90+
/** Index settings. */
91+
protected static JSONObject settings = new JSONObject();
92+
static {
93+
try {
94+
settings
95+
.put("searchableAttributes", new JSONArray()
96+
.put("name").put("kind").put("series")
97+
)
98+
.put("attributesForFaceting", new JSONArray().put("searchable(series)")
99+
);
100+
}
101+
catch (JSONException e) {
102+
throw new RuntimeException(e); // should never happen
103+
}
104+
}
105+
90106
@Override
91107
public void setUp() throws Exception {
92108
super.setUp();

0 commit comments

Comments
 (0)