Skip to content

Commit 51feb42

Browse files
committed
HLRC: Add rollup search (#36334)
Relates to #29827
1 parent caed030 commit 51feb42

File tree

13 files changed

+272
-13
lines changed

13 files changed

+272
-13
lines changed

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

+9-2
Original file line numberDiff line numberDiff line change
@@ -361,8 +361,15 @@ static Request update(UpdateRequest updateRequest) throws IOException {
361361
return request;
362362
}
363363

364-
static Request search(SearchRequest searchRequest) throws IOException {
365-
Request request = new Request(HttpPost.METHOD_NAME, endpoint(searchRequest.indices(), searchRequest.types(), "_search"));
364+
/**
365+
* Convert a {@linkplain SearchRequest} into a {@linkplain Request}.
366+
* @param searchRequest the request to convert
367+
* @param searchEndpoint the name of the search endpoint. {@literal _search}
368+
* for standard searches and {@literal _rollup_search} for rollup
369+
* searches.
370+
*/
371+
static Request search(SearchRequest searchRequest, String searchEndpoint) throws IOException {
372+
Request request = new Request(HttpPost.METHOD_NAME, endpoint(searchRequest.indices(), searchRequest.types(), searchEndpoint));
366373

367374
Params params = new Params(request);
368375
addSearchRequestParams(params, searchRequest);

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

+25-5
Original file line numberDiff line numberDiff line change
@@ -1087,7 +1087,12 @@ public final void deleteAsync(DeleteRequest deleteRequest, ActionListener<Delete
10871087
* @return the response
10881088
*/
10891089
public final SearchResponse search(SearchRequest searchRequest, RequestOptions options) throws IOException {
1090-
return performRequestAndParseEntity(searchRequest, RequestConverters::search, options, SearchResponse::fromXContent, emptySet());
1090+
return performRequestAndParseEntity(
1091+
searchRequest,
1092+
r -> RequestConverters.search(r, "_search"),
1093+
options,
1094+
SearchResponse::fromXContent,
1095+
emptySet());
10911096
}
10921097

10931098
/**
@@ -1098,7 +1103,12 @@ public final SearchResponse search(SearchRequest searchRequest, RequestOptions o
10981103
*/
10991104
@Deprecated
11001105
public final SearchResponse search(SearchRequest searchRequest, Header... headers) throws IOException {
1101-
return performRequestAndParseEntity(searchRequest, RequestConverters::search, SearchResponse::fromXContent, emptySet(), headers);
1106+
return performRequestAndParseEntity(
1107+
searchRequest,
1108+
r -> RequestConverters.search(r, "_search"),
1109+
SearchResponse::fromXContent,
1110+
emptySet(),
1111+
headers);
11021112
}
11031113

11041114
/**
@@ -1109,7 +1119,12 @@ public final SearchResponse search(SearchRequest searchRequest, Header... header
11091119
* @param listener the listener to be notified upon request completion
11101120
*/
11111121
public final void searchAsync(SearchRequest searchRequest, RequestOptions options, ActionListener<SearchResponse> listener) {
1112-
performRequestAsyncAndParseEntity(searchRequest, RequestConverters::search, options, SearchResponse::fromXContent, listener,
1122+
performRequestAsyncAndParseEntity(
1123+
searchRequest,
1124+
r -> RequestConverters.search(r, "_search"),
1125+
options,
1126+
SearchResponse::fromXContent,
1127+
listener,
11131128
emptySet());
11141129
}
11151130

@@ -1121,8 +1136,13 @@ public final void searchAsync(SearchRequest searchRequest, RequestOptions option
11211136
*/
11221137
@Deprecated
11231138
public final void searchAsync(SearchRequest searchRequest, ActionListener<SearchResponse> listener, Header... headers) {
1124-
performRequestAsyncAndParseEntity(searchRequest, RequestConverters::search, SearchResponse::fromXContent, listener,
1125-
emptySet(), headers);
1139+
performRequestAsyncAndParseEntity(
1140+
searchRequest,
1141+
r -> RequestConverters.search(r, "_search"),
1142+
SearchResponse::fromXContent,
1143+
listener,
1144+
emptySet(),
1145+
headers);
11261146
}
11271147

11281148
/**

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

+38
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
package org.elasticsearch.client;
2121

2222
import org.elasticsearch.action.ActionListener;
23+
import org.elasticsearch.action.search.SearchRequest;
24+
import org.elasticsearch.action.search.SearchResponse;
2325
import org.elasticsearch.client.core.AcknowledgedResponse;
2426
import org.elasticsearch.client.rollup.DeleteRollupJobRequest;
2527
import org.elasticsearch.client.rollup.GetRollupIndexCapsRequest;
@@ -224,6 +226,42 @@ public void getRollupJobAsync(GetRollupJobRequest request, RequestOptions option
224226
listener, Collections.emptySet());
225227
}
226228

229+
/**
230+
* Perform a rollup search.
231+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-search.html">
232+
* the docs</a> for more.
233+
* @param request the request
234+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
235+
* @return the response
236+
* @throws IOException in case there is a problem sending the request or parsing back the response
237+
*/
238+
public SearchResponse search(SearchRequest request, RequestOptions options) throws IOException {
239+
return restHighLevelClient.performRequestAndParseEntity(
240+
request,
241+
RollupRequestConverters::search,
242+
options,
243+
SearchResponse::fromXContent,
244+
Collections.emptySet());
245+
}
246+
247+
/**
248+
* Perform a rollup search.
249+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-search.html">
250+
* the docs</a> for more.
251+
* @param request the request
252+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
253+
* @param listener the listener to be notified upon request completion
254+
*/
255+
public void searchAsync(SearchRequest request, RequestOptions options, ActionListener<SearchResponse> listener) {
256+
restHighLevelClient.performRequestAsyncAndParseEntity(
257+
request,
258+
RollupRequestConverters::search,
259+
options,
260+
SearchResponse::fromXContent,
261+
listener,
262+
Collections.emptySet());
263+
}
264+
227265
/**
228266
* Get the Rollup Capabilities of a target (non-rollup) index or pattern
229267
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/master/rollup-get-rollup-caps.html">

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

+15
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.apache.http.client.methods.HttpGet;
2323
import org.apache.http.client.methods.HttpPost;
2424
import org.apache.http.client.methods.HttpPut;
25+
import org.elasticsearch.action.search.SearchRequest;
2526
import org.elasticsearch.client.rollup.DeleteRollupJobRequest;
2627
import org.elasticsearch.client.rollup.GetRollupCapsRequest;
2728
import org.elasticsearch.client.rollup.GetRollupIndexCapsRequest;
@@ -83,6 +84,20 @@ static Request getJob(final GetRollupJobRequest getRollupJobRequest) {
8384
return new Request(HttpGet.METHOD_NAME, endpoint);
8485
}
8586

87+
static Request search(final SearchRequest request) throws IOException {
88+
if (request.types().length > 0) {
89+
/*
90+
* Ideally we'd check this with the standard validation framework
91+
* but we don't have a special request for rollup search so that'd
92+
* be difficult.
93+
*/
94+
ValidationException ve = new ValidationException();
95+
ve.addValidationError("types are not allowed in rollup search");
96+
throw ve;
97+
}
98+
return RequestConverters.search(request, "_rollup_search");
99+
}
100+
86101
static Request getRollupCaps(final GetRollupCapsRequest getRollupCapsRequest) throws IOException {
87102
String endpoint = new RequestConverters.EndpointBuilder()
88103
.addPathPartAsIs("_xpack", "rollup", "data")

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

+6-4
Original file line numberDiff line numberDiff line change
@@ -908,14 +908,16 @@ public void testGlobalPipelineOnBulkRequest() throws IOException {
908908
}
909909

910910
public void testSearchNullSource() throws IOException {
911+
String searchEndpoint = randomFrom("_" + randomAlphaOfLength(5));
911912
SearchRequest searchRequest = new SearchRequest();
912-
Request request = RequestConverters.search(searchRequest);
913+
Request request = RequestConverters.search(searchRequest, searchEndpoint);
913914
assertEquals(HttpPost.METHOD_NAME, request.getMethod());
914-
assertEquals("/_search", request.getEndpoint());
915+
assertEquals("/" + searchEndpoint, request.getEndpoint());
915916
assertNull(request.getEntity());
916917
}
917918

918919
public void testSearch() throws Exception {
920+
String searchEndpoint = randomFrom("_" + randomAlphaOfLength(5));
919921
String[] indices = randomIndicesNames(0, 5);
920922
SearchRequest searchRequest = new SearchRequest(indices);
921923

@@ -976,7 +978,7 @@ public void testSearch() throws Exception {
976978
searchRequest.source(searchSourceBuilder);
977979
}
978980

979-
Request request = RequestConverters.search(searchRequest);
981+
Request request = RequestConverters.search(searchRequest, searchEndpoint);
980982
StringJoiner endpoint = new StringJoiner("/", "/", "");
981983
String index = String.join(",", indices);
982984
if (Strings.hasLength(index)) {
@@ -986,7 +988,7 @@ public void testSearch() throws Exception {
986988
if (Strings.hasLength(type)) {
987989
endpoint.add(type);
988990
}
989-
endpoint.add("_search");
991+
endpoint.add(searchEndpoint);
990992
assertEquals(HttpPost.METHOD_NAME, request.getMethod());
991993
assertEquals(endpoint.toString(), request.getEndpoint());
992994
assertEquals(expectedParams, request.getParameters());

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

+30
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,13 @@
5454
import org.elasticsearch.rest.RestStatus;
5555
import org.elasticsearch.search.SearchHit;
5656
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
57+
import org.elasticsearch.search.aggregations.metrics.NumericMetricsAggregation;
5758
import org.elasticsearch.search.aggregations.metrics.avg.AvgAggregationBuilder;
5859
import org.elasticsearch.search.aggregations.metrics.max.MaxAggregationBuilder;
5960
import org.elasticsearch.search.aggregations.metrics.min.MinAggregationBuilder;
6061
import org.elasticsearch.search.aggregations.metrics.sum.SumAggregationBuilder;
6162
import org.elasticsearch.search.aggregations.metrics.valuecount.ValueCountAggregationBuilder;
63+
import org.elasticsearch.search.builder.SearchSourceBuilder;
6264
import org.junit.Before;
6365

6466
import java.util.Arrays;
@@ -70,6 +72,7 @@
7072
import java.util.Set;
7173

7274
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
75+
import static org.hamcrest.Matchers.closeTo;
7376
import static org.hamcrest.Matchers.either;
7477
import static org.hamcrest.Matchers.empty;
7578
import static org.hamcrest.Matchers.equalTo;
@@ -245,6 +248,33 @@ public void testPutAndGetRollupJob() throws Exception {
245248
}
246249
}
247250

251+
public void testSearch() throws Exception {
252+
testPutAndGetRollupJob();
253+
SearchRequest search = new SearchRequest(rollupIndex);
254+
search.source(new SearchSourceBuilder()
255+
.size(0)
256+
.aggregation(new AvgAggregationBuilder("avg").field("value")));
257+
SearchResponse response = highLevelClient().rollup().search(search, RequestOptions.DEFAULT);
258+
assertEquals(0, response.getFailedShards());
259+
assertEquals(0, response.getHits().getTotalHits());
260+
NumericMetricsAggregation.SingleValue avg = response.getAggregations().get("avg");
261+
assertThat(avg.value(), closeTo(sum / numDocs, 0.00000001));
262+
}
263+
264+
public void testSearchWithType() throws Exception {
265+
SearchRequest search = new SearchRequest(rollupIndex);
266+
search.types("a", "b", "c");
267+
search.source(new SearchSourceBuilder()
268+
.size(0)
269+
.aggregation(new AvgAggregationBuilder("avg").field("value")));
270+
try {
271+
highLevelClient().rollup().search(search, RequestOptions.DEFAULT);
272+
fail("types are not allowed but didn't fail");
273+
} catch (ValidationException e) {
274+
assertEquals("Validation Failed: 1: types are not allowed in rollup search;", e.getMessage());
275+
}
276+
}
277+
248278
public void testGetMissingRollupJob() throws Exception {
249279
GetRollupJobRequest getRollupJobRequest = new GetRollupJobRequest("missing");
250280
RollupClient rollupClient = highLevelClient().rollup();

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

+57-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import org.elasticsearch.action.bulk.BulkRequest;
2828
import org.elasticsearch.action.bulk.BulkResponse;
2929
import org.elasticsearch.action.index.IndexRequest;
30+
import org.elasticsearch.action.search.SearchRequest;
31+
import org.elasticsearch.action.search.SearchResponse;
3032
import org.elasticsearch.action.support.WriteRequest;
3133
import org.elasticsearch.client.ESRestHighLevelClientTestCase;
3234
import org.elasticsearch.client.RequestOptions;
@@ -60,6 +62,9 @@
6062
import org.elasticsearch.common.unit.TimeValue;
6163
import org.elasticsearch.rest.RestStatus;
6264
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
65+
import org.elasticsearch.search.aggregations.metrics.NumericMetricsAggregation;
66+
import org.elasticsearch.search.aggregations.metrics.max.MaxAggregationBuilder;
67+
import org.elasticsearch.search.builder.SearchSourceBuilder;
6368
import org.junit.Before;
6469

6570
import java.io.IOException;
@@ -72,6 +77,7 @@
7277
import java.util.concurrent.TimeUnit;
7378

7479
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
80+
import static org.hamcrest.Matchers.closeTo;
7581
import static org.hamcrest.Matchers.equalTo;
7682
import static org.hamcrest.Matchers.hasSize;
7783
import static org.hamcrest.Matchers.isOneOf;
@@ -89,7 +95,7 @@ public void setUpDocs() throws IOException {
8995
.field("timestamp", String.format(Locale.ROOT, "2018-01-01T00:%02d:00Z", i))
9096
.field("hostname", 0)
9197
.field("datacenter", 0)
92-
.field("temperature", 0)
98+
.field("temperature", i)
9399
.field("voltage", 0)
94100
.field("load", 0)
95101
.field("net_in", 0)
@@ -330,6 +336,56 @@ public void onFailure(Exception e) {
330336
assertTrue(latch.await(30L, TimeUnit.SECONDS));
331337
}
332338

339+
public void testSearch() throws Exception {
340+
// Setup a rollup index to query
341+
testCreateRollupJob();
342+
343+
RestHighLevelClient client = highLevelClient();
344+
345+
// tag::search-request
346+
SearchRequest request = new SearchRequest();
347+
request.source(new SearchSourceBuilder()
348+
.size(0)
349+
.aggregation(new MaxAggregationBuilder("max_temperature")
350+
.field("temperature")));
351+
// end::search-request
352+
353+
// tag::search-execute
354+
SearchResponse response =
355+
client.rollup().search(request, RequestOptions.DEFAULT);
356+
// end::search-execute
357+
358+
// tag::search-response
359+
NumericMetricsAggregation.SingleValue maxTemperature =
360+
response.getAggregations().get("max_temperature");
361+
assertThat(maxTemperature.value(), closeTo(49.0, .00001));
362+
// end::search-response
363+
364+
ActionListener<SearchResponse> listener;
365+
// tag::search-execute-listener
366+
listener = new ActionListener<SearchResponse>() {
367+
@Override
368+
public void onResponse(SearchResponse response) {
369+
// <1>
370+
}
371+
372+
@Override
373+
public void onFailure(Exception e) {
374+
// <2>
375+
}
376+
};
377+
// end::search-execute-listener
378+
379+
final CountDownLatch latch = new CountDownLatch(1);
380+
listener = new LatchedActionListener<>(listener, latch);
381+
382+
// tag::search-execute-async
383+
client.rollup().searchAsync(request, RequestOptions.DEFAULT, listener); // <1>
384+
// end::search-execute-async
385+
386+
assertTrue(latch.await(30L, TimeUnit.SECONDS));
387+
}
388+
333389
@SuppressWarnings("unused")
334390
public void testGetRollupCaps() throws Exception {
335391
RestHighLevelClient client = highLevelClient();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
--
2+
:api: search
3+
:request: SearchRequest
4+
:response: SearchResponse
5+
--
6+
7+
[id="{upid}-{api}"]
8+
=== Rollup Search API
9+
10+
The Rollup Search endpoint allows searching rolled-up data using the standard
11+
query DSL. The Rollup Search endpoint is needed because, internally,
12+
rolled-up documents utilize a different document structure than the original
13+
data. The Rollup Search endpoint rewrites standard query DSL into a format that
14+
matches the rollup documents, then takes the response and rewrites it back to
15+
what a client would expect given the original query.
16+
17+
[id="{upid}-{api}-request"]
18+
==== Request
19+
20+
Rollup Search uses the same +{request}+ that is used by the <<{mainid}-search>>
21+
but it is mostly for aggregations you should set the `size` to 0 and add
22+
aggregations like this:
23+
24+
["source","java",subs="attributes,callouts,macros"]
25+
--------------------------------------------------
26+
include-tagged::{doc-tests-file}[{api}-request]
27+
--------------------------------------------------
28+
29+
NOTE:: Rollup Search is limited in many ways because only some query elements
30+
can be translated into queries against the rollup indices. See the main
31+
{ref}/rollup-search.html[Rollup Search] documentation for more.
32+
33+
include::../execution.asciidoc[]
34+
35+
[id="{upid}-{api}-response"]
36+
==== Response
37+
38+
Rollup Search returns the same +{response}+ that is used by the
39+
<<{mainid}-search>> and everything can be accessed in exactly the same way.
40+
This will access the aggregation built by the example request above:
41+
42+
["source","java",subs="attributes,callouts,macros"]
43+
--------------------------------------------------
44+
include-tagged::{doc-tests-file}[{api}-response]
45+
--------------------------------------------------

0 commit comments

Comments
 (0)