Skip to content

Commit 3c8784f

Browse files
committed
Add support for field capabilities to the high-level REST client. (elastic#29664)
(cherry picked from commit d40116d)
1 parent 122ce76 commit 3c8784f

File tree

13 files changed

+591
-55
lines changed

13 files changed

+591
-55
lines changed

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,14 @@
4747
import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
4848
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
4949
import org.elasticsearch.action.admin.indices.rollover.RolloverRequest;
50-
import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequest;
5150
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
51+
import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequest;
5252
import org.elasticsearch.action.admin.indices.shrink.ResizeRequest;
5353
import org.elasticsearch.action.admin.indices.shrink.ResizeType;
5454
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
5555
import org.elasticsearch.action.bulk.BulkRequest;
5656
import org.elasticsearch.action.delete.DeleteRequest;
57+
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequest;
5758
import org.elasticsearch.action.get.GetRequest;
5859
import org.elasticsearch.action.get.MultiGetRequest;
5960
import org.elasticsearch.action.index.IndexRequest;
@@ -535,6 +536,17 @@ static Request existsAlias(GetAliasesRequest getAliasesRequest) {
535536
return request;
536537
}
537538

539+
static Request fieldCaps(FieldCapabilitiesRequest fieldCapabilitiesRequest) {
540+
String[] indices = fieldCapabilitiesRequest.indices();
541+
Request request = new Request(HttpGet.METHOD_NAME, endpoint(indices, "_field_caps"));
542+
543+
Params params = new Params(request);
544+
params.withFields(fieldCapabilitiesRequest.fields());
545+
params.withIndicesOptions(fieldCapabilitiesRequest.indicesOptions());
546+
547+
return request;
548+
}
549+
538550
static Request rankEval(RankEvalRequest rankEvalRequest) throws IOException {
539551
Request request = new Request(HttpGet.METHOD_NAME, endpoint(rankEvalRequest.indices(), Strings.EMPTY_ARRAY, "_rank_eval"));
540552

@@ -806,6 +818,13 @@ Params withFetchSourceContext(FetchSourceContext fetchSourceContext) {
806818
return this;
807819
}
808820

821+
Params withFields(String[] fields) {
822+
if (fields != null && fields.length > 0) {
823+
return putParam("fields", String.join(",", fields));
824+
}
825+
return this;
826+
}
827+
809828
Params withMasterTimeout(TimeValue masterTimeout) {
810829
return putParam("master_timeout", masterTimeout);
811830
}

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@
2626
import org.elasticsearch.action.ActionListener;
2727
import org.elasticsearch.action.ActionRequest;
2828
import org.elasticsearch.action.ActionRequestValidationException;
29-
import org.elasticsearch.action.admin.cluster.repositories.get.GetRepositoriesRequest;
30-
import org.elasticsearch.action.admin.cluster.repositories.get.GetRepositoriesResponse;
3129
import org.elasticsearch.action.bulk.BulkRequest;
3230
import org.elasticsearch.action.bulk.BulkResponse;
3331
import org.elasticsearch.action.delete.DeleteRequest;
3432
import org.elasticsearch.action.delete.DeleteResponse;
33+
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequest;
34+
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesResponse;
3535
import org.elasticsearch.action.get.GetRequest;
3636
import org.elasticsearch.action.get.GetResponse;
3737
import org.elasticsearch.action.get.MultiGetRequest;
@@ -521,6 +521,31 @@ public final void rankEvalAsync(RankEvalRequest rankEvalRequest, ActionListener<
521521
emptySet(), headers);
522522
}
523523

524+
/**
525+
* Executes a request using the Field Capabilities API.
526+
*
527+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-field-caps.html">Field Capabilities API
528+
* on elastic.co</a>.
529+
*/
530+
public final FieldCapabilitiesResponse fieldCaps(FieldCapabilitiesRequest fieldCapabilitiesRequest,
531+
Header... headers) throws IOException {
532+
return performRequestAndParseEntity(fieldCapabilitiesRequest, RequestConverters::fieldCaps,
533+
FieldCapabilitiesResponse::fromXContent, emptySet(), headers);
534+
}
535+
536+
/**
537+
* Asynchronously executes a request using the Field Capabilities API.
538+
*
539+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-field-caps.html">Field Capabilities API
540+
* on elastic.co</a>.
541+
*/
542+
public final void fieldCapsAsync(FieldCapabilitiesRequest fieldCapabilitiesRequest,
543+
ActionListener<FieldCapabilitiesResponse> listener,
544+
Header... headers) {
545+
performRequestAsyncAndParseEntity(fieldCapabilitiesRequest, RequestConverters::fieldCaps,
546+
FieldCapabilitiesResponse::fromXContent, listener, emptySet(), headers);
547+
}
548+
524549
protected final <Req extends ActionRequest, Resp> Resp performRequestAndParseEntity(Req request,
525550
CheckedFunction<Req, Request, IOException> requestConverter,
526551
CheckedFunction<XContentParser, Resp, IOException> entityParser,

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

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,15 @@
4949
import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
5050
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
5151
import org.elasticsearch.action.admin.indices.rollover.RolloverRequest;
52-
import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequest;
5352
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
53+
import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequest;
5454
import org.elasticsearch.action.admin.indices.shrink.ResizeRequest;
5555
import org.elasticsearch.action.admin.indices.shrink.ResizeType;
5656
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
5757
import org.elasticsearch.action.bulk.BulkRequest;
5858
import org.elasticsearch.action.bulk.BulkShardRequest;
5959
import org.elasticsearch.action.delete.DeleteRequest;
60+
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequest;
6061
import org.elasticsearch.action.get.GetRequest;
6162
import org.elasticsearch.action.get.MultiGetRequest;
6263
import org.elasticsearch.action.index.IndexRequest;
@@ -121,6 +122,7 @@
121122
import java.util.Arrays;
122123
import java.util.Collections;
123124
import java.util.HashMap;
125+
import java.util.HashSet;
124126
import java.util.List;
125127
import java.util.Locale;
126128
import java.util.Map;
@@ -139,6 +141,8 @@
139141
import static org.elasticsearch.search.RandomSearchRequestGenerator.randomSearchRequest;
140142
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent;
141143
import static org.hamcrest.CoreMatchers.equalTo;
144+
import static org.hamcrest.Matchers.hasEntry;
145+
import static org.hamcrest.Matchers.hasKey;
142146
import static org.hamcrest.Matchers.notNullValue;
143147
import static org.hamcrest.Matchers.nullValue;
144148

@@ -1271,6 +1275,47 @@ public void testExistsAliasNoAliasNoIndex() {
12711275
}
12721276
}
12731277

1278+
public void testFieldCaps() {
1279+
// Create a random request.
1280+
String[] indices = randomIndicesNames(0, 5);
1281+
String[] fields = generateRandomStringArray(5, 10, false, false);
1282+
1283+
FieldCapabilitiesRequest fieldCapabilitiesRequest = new FieldCapabilitiesRequest()
1284+
.indices(indices)
1285+
.fields(fields);
1286+
1287+
Map<String, String> indicesOptionsParams = new HashMap<>();
1288+
setRandomIndicesOptions(fieldCapabilitiesRequest::indicesOptions,
1289+
fieldCapabilitiesRequest::indicesOptions,
1290+
indicesOptionsParams);
1291+
1292+
Request request = RequestConverters.fieldCaps(fieldCapabilitiesRequest);
1293+
1294+
// Verify that the resulting REST request looks as expected.
1295+
StringJoiner endpoint = new StringJoiner("/", "/", "");
1296+
String joinedIndices = String.join(",", indices);
1297+
if (!joinedIndices.isEmpty()) {
1298+
endpoint.add(joinedIndices);
1299+
}
1300+
endpoint.add("_field_caps");
1301+
1302+
assertEquals(endpoint.toString(), request.getEndpoint());
1303+
assertEquals(4, request.getParameters().size());
1304+
1305+
// Note that we don't check the field param value explicitly, as field names are passed through
1306+
// a hash set before being added to the request, and can appear in a non-deterministic order.
1307+
assertThat(request.getParameters(), hasKey("fields"));
1308+
String[] requestFields = Strings.splitStringByCommaToArray(request.getParameters().get("fields"));
1309+
assertEquals(new HashSet<>(Arrays.asList(fields)),
1310+
new HashSet<>(Arrays.asList(requestFields)));
1311+
1312+
for (Map.Entry<String, String> param : indicesOptionsParams.entrySet()) {
1313+
assertThat(request.getParameters(), hasEntry(param.getKey(), param.getValue()));
1314+
}
1315+
1316+
assertNull(request.getEntity());
1317+
}
1318+
12741319
public void testRankEval() throws Exception {
12751320
RankEvalSpec spec = new RankEvalSpec(
12761321
Collections.singletonList(new RatedRequest("queryId", Collections.emptyList(), new SearchSourceBuilder())),
@@ -1291,7 +1336,6 @@ public void testRankEval() throws Exception {
12911336
assertEquals(3, request.getParameters().size());
12921337
assertEquals(expectedParams, request.getParameters());
12931338
assertToXContentBody(spec, request.getEntity());
1294-
12951339
}
12961340

12971341
public void testSplit() throws IOException {

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

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
import org.apache.http.nio.entity.NStringEntity;
2828
import org.elasticsearch.ElasticsearchException;
2929
import org.elasticsearch.ElasticsearchStatusException;
30+
import org.elasticsearch.action.fieldcaps.FieldCapabilities;
31+
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequest;
32+
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesResponse;
3033
import org.elasticsearch.action.search.ClearScrollRequest;
3134
import org.elasticsearch.action.search.ClearScrollResponse;
3235
import org.elasticsearch.action.search.MultiSearchRequest;
@@ -96,14 +99,31 @@ public void indexDocuments() throws IOException {
9699
client().performRequest(HttpPut.METHOD_NAME, "/index/type/5", Collections.emptyMap(), doc5);
97100
client().performRequest(HttpPost.METHOD_NAME, "/index/_refresh");
98101

99-
StringEntity doc = new StringEntity("{\"field\":\"value1\"}", ContentType.APPLICATION_JSON);
102+
103+
StringEntity doc = new StringEntity("{\"field\":\"value1\", \"rating\": 7}", ContentType.APPLICATION_JSON);
100104
client().performRequest(HttpPut.METHOD_NAME, "/index1/doc/1", Collections.emptyMap(), doc);
101105
doc = new StringEntity("{\"field\":\"value2\"}", ContentType.APPLICATION_JSON);
102106
client().performRequest(HttpPut.METHOD_NAME, "/index1/doc/2", Collections.emptyMap(), doc);
103-
doc = new StringEntity("{\"field\":\"value1\"}", ContentType.APPLICATION_JSON);
107+
108+
StringEntity mappings = new StringEntity(
109+
"{" +
110+
" \"mappings\": {" +
111+
" \"doc\": {" +
112+
" \"properties\": {" +
113+
" \"rating\": {" +
114+
" \"type\": \"keyword\"" +
115+
" }" +
116+
" }" +
117+
" }" +
118+
" }" +
119+
"}}",
120+
ContentType.APPLICATION_JSON);
121+
client().performRequest("PUT", "/index2", Collections.emptyMap(), mappings);
122+
doc = new StringEntity("{\"field\":\"value1\", \"rating\": \"good\"}", ContentType.APPLICATION_JSON);
104123
client().performRequest(HttpPut.METHOD_NAME, "/index2/doc/3", Collections.emptyMap(), doc);
105124
doc = new StringEntity("{\"field\":\"value2\"}", ContentType.APPLICATION_JSON);
106125
client().performRequest(HttpPut.METHOD_NAME, "/index2/doc/4", Collections.emptyMap(), doc);
126+
107127
doc = new StringEntity("{\"field\":\"value1\"}", ContentType.APPLICATION_JSON);
108128
client().performRequest(HttpPut.METHOD_NAME, "/index3/doc/5", Collections.emptyMap(), doc);
109129
doc = new StringEntity("{\"field\":\"value2\"}", ContentType.APPLICATION_JSON);
@@ -708,6 +728,57 @@ public void testMultiSearch_failure() throws Exception {
708728
assertThat(multiSearchResponse.getResponses()[1].getResponse(), nullValue());
709729
}
710730

731+
public void testFieldCaps() throws IOException {
732+
FieldCapabilitiesRequest request = new FieldCapabilitiesRequest()
733+
.indices("index1", "index2")
734+
.fields("rating", "field");
735+
736+
FieldCapabilitiesResponse response = execute(request,
737+
highLevelClient()::fieldCaps, highLevelClient()::fieldCapsAsync);
738+
739+
// Check the capabilities for the 'rating' field.
740+
assertTrue(response.get().containsKey("rating"));
741+
Map<String, FieldCapabilities> ratingResponse = response.getField("rating");
742+
assertEquals(2, ratingResponse.size());
743+
744+
FieldCapabilities expectedKeywordCapabilities = new FieldCapabilities(
745+
"rating", "keyword", true, true, new String[]{"index2"}, null, null);
746+
assertEquals(expectedKeywordCapabilities, ratingResponse.get("keyword"));
747+
748+
FieldCapabilities expectedLongCapabilities = new FieldCapabilities(
749+
"rating", "long", true, true, new String[]{"index1"}, null, null);
750+
assertEquals(expectedLongCapabilities, ratingResponse.get("long"));
751+
752+
// Check the capabilities for the 'field' field.
753+
assertTrue(response.get().containsKey("field"));
754+
Map<String, FieldCapabilities> fieldResponse = response.getField("field");
755+
assertEquals(1, fieldResponse.size());
756+
757+
FieldCapabilities expectedTextCapabilities = new FieldCapabilities(
758+
"field", "text", true, false);
759+
assertEquals(expectedTextCapabilities, fieldResponse.get("text"));
760+
}
761+
762+
public void testFieldCapsWithNonExistentFields() throws IOException {
763+
FieldCapabilitiesRequest request = new FieldCapabilitiesRequest()
764+
.indices("index2")
765+
.fields("nonexistent");
766+
767+
FieldCapabilitiesResponse response = execute(request,
768+
highLevelClient()::fieldCaps, highLevelClient()::fieldCapsAsync);
769+
assertTrue(response.get().isEmpty());
770+
}
771+
772+
public void testFieldCapsWithNonExistentIndices() {
773+
FieldCapabilitiesRequest request = new FieldCapabilitiesRequest()
774+
.indices("non-existent")
775+
.fields("rating");
776+
777+
ElasticsearchException exception = expectThrows(ElasticsearchException.class,
778+
() -> execute(request, highLevelClient()::fieldCaps, highLevelClient()::fieldCapsAsync));
779+
assertEquals(RestStatus.NOT_FOUND, exception.status());
780+
}
781+
711782
private static void assertSearchHeader(SearchResponse searchResponse) {
712783
assertThat(searchResponse.getTook().nanos(), greaterThanOrEqualTo(0L));
713784
assertEquals(0, searchResponse.getFailedShards());

0 commit comments

Comments
 (0)