Skip to content

Commit d47d479

Browse files
authored
Add support for clear scroll to high level REST client (#25038)
1 parent fbf2e3d commit d47d479

File tree

5 files changed

+130
-0
lines changed

5 files changed

+130
-0
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/Request.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.elasticsearch.action.delete.DeleteRequest;
3434
import org.elasticsearch.action.get.GetRequest;
3535
import org.elasticsearch.action.index.IndexRequest;
36+
import org.elasticsearch.action.search.ClearScrollRequest;
3637
import org.elasticsearch.action.search.SearchRequest;
3738
import org.elasticsearch.action.search.SearchScrollRequest;
3839
import org.elasticsearch.action.support.ActiveShardCount;
@@ -344,6 +345,11 @@ static Request searchScroll(SearchScrollRequest searchScrollRequest) throws IOEx
344345
return new Request("GET", "/_search/scroll", Collections.emptyMap(), entity);
345346
}
346347

348+
static Request clearScroll(ClearScrollRequest clearScrollRequest) throws IOException {
349+
HttpEntity entity = createEntity(clearScrollRequest, REQUEST_BODY_CONTENT_TYPE);
350+
return new Request("DELETE", "/_search/scroll", Collections.emptyMap(), entity);
351+
}
352+
347353
private static HttpEntity createEntity(ToXContent toXContent, XContentType xContentType) throws IOException {
348354
BytesRef source = XContentHelper.toXContent(toXContent, xContentType, false).toBytesRef();
349355
return new ByteArrayEntity(source.bytes, source.offset, source.length, ContentType.create(xContentType.mediaType()));

client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
import org.elasticsearch.action.index.IndexResponse;
3737
import org.elasticsearch.action.main.MainRequest;
3838
import org.elasticsearch.action.main.MainResponse;
39+
import org.elasticsearch.action.search.ClearScrollRequest;
40+
import org.elasticsearch.action.search.ClearScrollResponse;
3941
import org.elasticsearch.action.search.SearchRequest;
4042
import org.elasticsearch.action.search.SearchResponse;
4143
import org.elasticsearch.action.search.SearchScrollRequest;
@@ -347,6 +349,28 @@ public void searchScrollAsync(SearchScrollRequest searchScrollRequest, ActionLis
347349
listener, emptySet(), headers);
348350
}
349351

352+
/**
353+
* Clears one or more scroll ids using the Clear Scroll api
354+
*
355+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-scroll.html#_clear_scroll_api">
356+
* Clear Scroll API on elastic.co</a>
357+
*/
358+
public ClearScrollResponse clearScroll(ClearScrollRequest clearScrollRequest, Header... headers) throws IOException {
359+
return performRequestAndParseEntity(clearScrollRequest, Request::clearScroll, ClearScrollResponse::fromXContent,
360+
emptySet(), headers);
361+
}
362+
363+
/**
364+
* Asynchronously clears one or more scroll ids using the Clear Scroll api
365+
*
366+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-scroll.html#_clear_scroll_api">
367+
* Clear Scroll API on elastic.co</a>
368+
*/
369+
public void clearScrollAsync(ClearScrollRequest clearScrollRequest, ActionListener<ClearScrollResponse> listener, Header... headers) {
370+
performRequestAsyncAndParseEntity(clearScrollRequest, Request::clearScroll, ClearScrollResponse::fromXContent,
371+
listener, emptySet(), headers);
372+
}
373+
350374
private <Req extends ActionRequest, Resp> Resp performRequestAndParseEntity(Req request,
351375
CheckedFunction<Req, Request, IOException> requestConverter,
352376
CheckedFunction<XContentParser, Resp, IOException> entityParser,

client/rest-high-level/src/test/java/org/elasticsearch/client/RequestTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.elasticsearch.action.delete.DeleteRequest;
2929
import org.elasticsearch.action.get.GetRequest;
3030
import org.elasticsearch.action.index.IndexRequest;
31+
import org.elasticsearch.action.search.ClearScrollRequest;
3132
import org.elasticsearch.action.search.SearchRequest;
3233
import org.elasticsearch.action.search.SearchScrollRequest;
3334
import org.elasticsearch.action.search.SearchType;
@@ -731,6 +732,21 @@ public void testSearchScroll() throws IOException {
731732
assertEquals("/_search/scroll", request.endpoint);
732733
assertEquals(0, request.params.size());
733734
assertToXContentBody(searchScrollRequest, request.entity);
735+
assertEquals(Request.REQUEST_BODY_CONTENT_TYPE.mediaType(), request.entity.getContentType().getValue());
736+
}
737+
738+
public void testClearScroll() throws IOException {
739+
ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
740+
int numScrolls = randomIntBetween(1, 10);
741+
for (int i = 0; i < numScrolls; i++) {
742+
clearScrollRequest.addScrollId(randomAlphaOfLengthBetween(5, 10));
743+
}
744+
Request request = Request.clearScroll(clearScrollRequest);
745+
assertEquals("DELETE", request.method);
746+
assertEquals("/_search/scroll", request.endpoint);
747+
assertEquals(0, request.params.size());
748+
assertToXContentBody(clearScrollRequest, request.entity);
749+
assertEquals(Request.REQUEST_BODY_CONTENT_TYPE.mediaType(), request.entity.getContentType().getValue());
734750
}
735751

736752
private static void assertToXContentBody(ToXContent expectedBody, HttpEntity actualEntity) throws IOException {

client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
import org.elasticsearch.action.ActionRequestValidationException;
4343
import org.elasticsearch.action.main.MainRequest;
4444
import org.elasticsearch.action.main.MainResponse;
45+
import org.elasticsearch.action.search.ClearScrollRequest;
46+
import org.elasticsearch.action.search.ClearScrollResponse;
4547
import org.elasticsearch.action.search.SearchResponse;
4648
import org.elasticsearch.action.search.SearchResponseSections;
4749
import org.elasticsearch.action.search.SearchScrollRequest;
@@ -161,6 +163,19 @@ public void testSearchScroll() throws IOException {
161163
isNotNull(HttpEntity.class), argThat(new HeadersVarargMatcher(headers)));
162164
}
163165

166+
public void testClearScroll() throws IOException {
167+
Header[] headers = randomHeaders(random(), "Header");
168+
ClearScrollResponse mockClearScrollResponse = new ClearScrollResponse(randomBoolean(), randomIntBetween(0, Integer.MAX_VALUE));
169+
mockResponse(mockClearScrollResponse);
170+
ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
171+
clearScrollRequest.addScrollId(randomAlphaOfLengthBetween(5, 10));
172+
ClearScrollResponse clearScrollResponse = restHighLevelClient.clearScroll(clearScrollRequest, headers);
173+
assertEquals(mockClearScrollResponse.isSucceeded(), clearScrollResponse.isSucceeded());
174+
assertEquals(mockClearScrollResponse.getNumFreed(), clearScrollResponse.getNumFreed());
175+
verify(restClient).performRequest(eq("DELETE"), eq("/_search/scroll"), eq(Collections.emptyMap()),
176+
isNotNull(HttpEntity.class), argThat(new HeadersVarargMatcher(headers)));
177+
}
178+
164179
private void mockResponse(ToXContent toXContent) throws IOException {
165180
Response response = mock(Response.class);
166181
ContentType contentType = ContentType.parse(Request.REQUEST_BODY_CONTENT_TYPE.mediaType());

client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,19 @@
1919

2020
package org.elasticsearch.client;
2121

22+
import org.apache.http.HttpEntity;
2223
import org.apache.http.entity.ContentType;
2324
import org.apache.http.entity.StringEntity;
25+
import org.apache.http.nio.entity.NStringEntity;
26+
import org.elasticsearch.ElasticsearchException;
2427
import org.elasticsearch.ElasticsearchStatusException;
28+
import org.elasticsearch.action.search.ClearScrollRequest;
29+
import org.elasticsearch.action.search.ClearScrollResponse;
2530
import org.elasticsearch.action.search.SearchRequest;
2631
import org.elasticsearch.action.search.SearchResponse;
32+
import org.elasticsearch.action.search.SearchScrollRequest;
33+
import org.elasticsearch.common.unit.TimeValue;
34+
import org.elasticsearch.common.xcontent.XContentBuilder;
2735
import org.elasticsearch.index.query.MatchQueryBuilder;
2836
import org.elasticsearch.join.aggregations.Children;
2937
import org.elasticsearch.join.aggregations.ChildrenAggregationBuilder;
@@ -37,6 +45,7 @@
3745
import org.elasticsearch.search.aggregations.matrix.stats.MatrixStatsAggregationBuilder;
3846
import org.elasticsearch.search.aggregations.support.ValueType;
3947
import org.elasticsearch.search.builder.SearchSourceBuilder;
48+
import org.elasticsearch.search.sort.SortOrder;
4049
import org.elasticsearch.search.suggest.Suggest;
4150
import org.elasticsearch.search.suggest.SuggestBuilder;
4251
import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder;
@@ -46,11 +55,14 @@
4655
import java.util.Arrays;
4756
import java.util.Collections;
4857

58+
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
4959
import static org.hamcrest.Matchers.both;
60+
import static org.hamcrest.Matchers.containsString;
5061
import static org.hamcrest.Matchers.either;
5162
import static org.hamcrest.Matchers.equalTo;
5263
import static org.hamcrest.Matchers.greaterThan;
5364
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
65+
import static org.hamcrest.Matchers.instanceOf;
5466
import static org.hamcrest.Matchers.lessThan;
5567

5668
public class SearchIT extends ESRestHighLevelClientTestCase {
@@ -386,6 +398,63 @@ public void testSearchWithSuggest() throws IOException {
386398
}
387399
}
388400

401+
public void testSearchScroll() throws Exception {
402+
403+
for (int i = 0; i < 100; i++) {
404+
XContentBuilder builder = jsonBuilder().startObject().field("field", i).endObject();
405+
HttpEntity entity = new NStringEntity(builder.string(), ContentType.APPLICATION_JSON);
406+
client().performRequest("PUT", "test/type1/" + Integer.toString(i), Collections.emptyMap(), entity);
407+
}
408+
client().performRequest("POST", "/test/_refresh");
409+
410+
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().size(35).sort("field", SortOrder.ASC);
411+
SearchRequest searchRequest = new SearchRequest("test").scroll(TimeValue.timeValueMinutes(2)).source(searchSourceBuilder);
412+
SearchResponse searchResponse = execute(searchRequest, highLevelClient()::search, highLevelClient()::searchAsync);
413+
414+
try {
415+
long counter = 0;
416+
assertSearchHeader(searchResponse);
417+
assertThat(searchResponse.getHits().getTotalHits(), equalTo(100L));
418+
assertThat(searchResponse.getHits().getHits().length, equalTo(35));
419+
for (SearchHit hit : searchResponse.getHits()) {
420+
assertThat(((Number) hit.getSortValues()[0]).longValue(), equalTo(counter++));
421+
}
422+
423+
searchResponse = execute(new SearchScrollRequest(searchResponse.getScrollId()).scroll(TimeValue.timeValueMinutes(2)),
424+
highLevelClient()::searchScroll, highLevelClient()::searchScrollAsync);
425+
426+
assertThat(searchResponse.getHits().getTotalHits(), equalTo(100L));
427+
assertThat(searchResponse.getHits().getHits().length, equalTo(35));
428+
for (SearchHit hit : searchResponse.getHits()) {
429+
assertEquals(counter++, ((Number) hit.getSortValues()[0]).longValue());
430+
}
431+
432+
searchResponse = execute(new SearchScrollRequest(searchResponse.getScrollId()).scroll(TimeValue.timeValueMinutes(2)),
433+
highLevelClient()::searchScroll, highLevelClient()::searchScrollAsync);
434+
435+
assertThat(searchResponse.getHits().getTotalHits(), equalTo(100L));
436+
assertThat(searchResponse.getHits().getHits().length, equalTo(30));
437+
for (SearchHit hit : searchResponse.getHits()) {
438+
assertEquals(counter++, ((Number) hit.getSortValues()[0]).longValue());
439+
}
440+
} finally {
441+
ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
442+
clearScrollRequest.addScrollId(searchResponse.getScrollId());
443+
ClearScrollResponse clearScrollResponse = execute(clearScrollRequest, highLevelClient()::clearScroll,
444+
highLevelClient()::clearScrollAsync);
445+
assertThat(clearScrollResponse.getNumFreed(), greaterThan(0));
446+
assertTrue(clearScrollResponse.isSucceeded());
447+
448+
SearchScrollRequest scrollRequest = new SearchScrollRequest(searchResponse.getScrollId()).scroll(TimeValue.timeValueMinutes(2));
449+
ElasticsearchStatusException exception = expectThrows(ElasticsearchStatusException.class, () -> execute(scrollRequest,
450+
highLevelClient()::searchScroll, highLevelClient()::searchScrollAsync));
451+
assertEquals(RestStatus.NOT_FOUND, exception.status());
452+
assertThat(exception.getRootCause(), instanceOf(ElasticsearchException.class));
453+
ElasticsearchException rootCause = (ElasticsearchException) exception.getRootCause();
454+
assertThat(rootCause.getMessage(), containsString("No search context found for"));
455+
}
456+
}
457+
389458
private static void assertSearchHeader(SearchResponse searchResponse) {
390459
assertThat(searchResponse.getTook().nanos(), greaterThanOrEqualTo(0L));
391460
assertEquals(0, searchResponse.getFailedShards());

0 commit comments

Comments
 (0)