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

Commit d327b8d

Browse files
author
Clément Le Provost
committed
Implement offline search for facet values in MirroredIndex
1 parent 7282e43 commit d327b8d

File tree

3 files changed

+179
-6
lines changed

3 files changed

+179
-6
lines changed

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/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/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)