diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/CRUDDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/CRUDDocumentationIT.java index a12bd48f222..5adc7bee273 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/CRUDDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/CRUDDocumentationIT.java @@ -37,10 +37,14 @@ import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteResponse; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.get.MultiGetItemResponse; +import org.elasticsearch.action.get.MultiGetRequest; +import org.elasticsearch.action.get.MultiGetResponse; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.action.support.ActiveShardCount; import org.elasticsearch.action.support.WriteRequest; +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy; import org.elasticsearch.action.support.replication.ReplicationResponse; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateResponse; @@ -68,6 +72,11 @@ import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.not; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; @@ -809,7 +818,7 @@ public class CRUDDocumentationIT extends ESRestHighLevelClientTestCase { { GetRequest request = new GetRequest("posts", "doc", "1"); //tag::get-request-no-source - request.fetchSourceContext(new FetchSourceContext(false)); // <1> + request.fetchSourceContext(FetchSourceContext.DO_NOT_FETCH_SOURCE); // <1> //end::get-request-no-source GetResponse getResponse = client.get(request); assertNull(getResponse.getSourceInternal()); @@ -1066,4 +1075,201 @@ public class CRUDDocumentationIT extends ESRestHighLevelClientTestCase { // end::bulk-processor-options } } + + public void testMultiGet() throws Exception { + RestHighLevelClient client = highLevelClient(); + + { + String mappings = "{\n" + + " \"mappings\" : {\n" + + " \"type\" : {\n" + + " \"properties\" : {\n" + + " \"foo\" : {\n" + + " \"type\": \"text\",\n" + + " \"store\": true\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + "}"; + + NStringEntity entity = new NStringEntity(mappings, ContentType.APPLICATION_JSON); + Response response = client().performRequest("PUT", "/index", Collections.emptyMap(), entity); + assertEquals(200, response.getStatusLine().getStatusCode()); + } + + Map source = new HashMap<>(); + source.put("foo", "val1"); + source.put("bar", "val2"); + source.put("baz", "val3"); + client.index(new IndexRequest("index", "type", "example_id") + .source(source) + .setRefreshPolicy(RefreshPolicy.IMMEDIATE)); + + { + // tag::multi-get-request + MultiGetRequest request = new MultiGetRequest(); + request.add(new MultiGetRequest.Item( + "index", // <1> + "type", // <2> + "example_id")); // <3> + request.add(new MultiGetRequest.Item("index", "type", "another_id")); // <4> + // end::multi-get-request + + // Add a missing index so we can test it. + request.add(new MultiGetRequest.Item("missing_index", "type", "id")); + + // tag::multi-get-request-item-extras + request.add(new MultiGetRequest.Item("index", "type", "with_routing") + .routing("some_routing")); // <1> + request.add(new MultiGetRequest.Item("index", "type", "with_parent") + .parent("some_parent")); // <2> + request.add(new MultiGetRequest.Item("index", "type", "with_version") + .versionType(VersionType.EXTERNAL) // <3> + .version(10123L)); // <4> + // end::multi-get-request-item-extras + // tag::multi-get-request-top-level-extras + request.preference("some_preference"); // <1> + request.realtime(false); // <2> + request.refresh(true); // <3> + // end::multi-get-request-top-level-extras + + // tag::multi-get-execute + MultiGetResponse response = client.multiGet(request); + // end::multi-get-execute + + // tag::multi-get-response + MultiGetItemResponse firstItem = response.getResponses()[0]; + assertNull(firstItem.getFailure()); // <1> + GetResponse firstGet = firstItem.getResponse(); // <2> + String index = firstItem.getIndex(); + String type = firstItem.getType(); + String id = firstItem.getId(); + if (firstGet.isExists()) { + long version = firstGet.getVersion(); + String sourceAsString = firstGet.getSourceAsString(); // <3> + Map sourceAsMap = firstGet.getSourceAsMap(); // <4> + byte[] sourceAsBytes = firstGet.getSourceAsBytes(); // <5> + } else { + // <6> + } + // end::multi-get-response + + assertTrue(firstGet.isExists()); + assertEquals(source, firstGet.getSource()); + + MultiGetItemResponse missingIndexItem = response.getResponses()[2]; + // tag::multi-get-indexnotfound + assertNull(missingIndexItem.getResponse()); // <1> + Exception e = missingIndexItem.getFailure().getFailure(); // <2> + ElasticsearchException ee = (ElasticsearchException) e; // <3> + // TODO status is broken! fix in a followup + // assertEquals(RestStatus.NOT_FOUND, ee.status()); // <4> + assertThat(e.getMessage(), + containsString("reason=no such index")); // <5> + // end::multi-get-indexnotfound + + // tag::multi-get-execute-listener + ActionListener listener = new ActionListener() { + @Override + public void onResponse(MultiGetResponse response) { + // <1> + } + + @Override + public void onFailure(Exception e) { + // <2> + } + }; + // end::multi-get-execute-listener + + // Replace the empty listener by a blocking listener in test + final CountDownLatch latch = new CountDownLatch(1); + listener = new LatchedActionListener<>(listener, latch); + + // tag::multi-get-execute-async + client.multiGetAsync(request, listener); // <1> + // end::multi-get-execute-async + + assertTrue(latch.await(30L, TimeUnit.SECONDS)); + } + { + MultiGetRequest request = new MultiGetRequest(); + // tag::multi-get-request-no-source + request.add(new MultiGetRequest.Item("index", "type", "example_id") + .fetchSourceContext(FetchSourceContext.DO_NOT_FETCH_SOURCE)); // <1> + // end::multi-get-request-no-source + MultiGetItemResponse item = unwrapAndAssertExample(client.multiGet(request)); + assertNull(item.getResponse().getSource()); + } + { + MultiGetRequest request = new MultiGetRequest(); + // tag::multi-get-request-source-include + String[] includes = new String[] {"foo", "*r"}; + String[] excludes = Strings.EMPTY_ARRAY; + FetchSourceContext fetchSourceContext = + new FetchSourceContext(true, includes, excludes); + request.add(new MultiGetRequest.Item("index", "type", "example_id") + .fetchSourceContext(fetchSourceContext)); // <1> + // end::multi-get-request-source-include + MultiGetItemResponse item = unwrapAndAssertExample(client.multiGet(request)); + assertThat(item.getResponse().getSource(), hasEntry("foo", "val1")); + assertThat(item.getResponse().getSource(), hasEntry("bar", "val2")); + assertThat(item.getResponse().getSource(), not(hasKey("baz"))); + } + { + MultiGetRequest request = new MultiGetRequest(); + // tag::multi-get-request-source-exclude + String[] includes = Strings.EMPTY_ARRAY; + String[] excludes = new String[] {"foo", "*r"}; + FetchSourceContext fetchSourceContext = + new FetchSourceContext(true, includes, excludes); + request.add(new MultiGetRequest.Item("index", "type", "example_id") + .fetchSourceContext(fetchSourceContext)); // <1> + // end::multi-get-request-source-exclude + MultiGetItemResponse item = unwrapAndAssertExample(client.multiGet(request)); + assertThat(item.getResponse().getSource(), not(hasKey("foo"))); + assertThat(item.getResponse().getSource(), not(hasKey("bar"))); + assertThat(item.getResponse().getSource(), hasEntry("baz", "val3")); + } + { + MultiGetRequest request = new MultiGetRequest(); + // tag::multi-get-request-stored + request.add(new MultiGetRequest.Item("index", "type", "example_id") + .storedFields("foo")); // <1> + MultiGetResponse response = client.multiGet(request); + MultiGetItemResponse item = response.getResponses()[0]; + String value = item.getResponse().getField("foo").getValue(); // <2> + // end::multi-get-request-stored + assertNull(item.getResponse().getSource()); + assertEquals("val1", value); + } + { + // tag::multi-get-conflict + MultiGetRequest request = new MultiGetRequest(); + request.add(new MultiGetRequest.Item("index", "type", "example_id") + .version(1000L)); + MultiGetResponse response = client.multiGet(request); + MultiGetItemResponse item = response.getResponses()[0]; + assertNull(item.getResponse()); // <1> + Exception e = item.getFailure().getFailure(); // <2> + ElasticsearchException ee = (ElasticsearchException) e; // <3> + // TODO status is broken! fix in a followup + // assertEquals(RestStatus.CONFLICT, ee.status()); // <4> + assertThat(e.getMessage(), + containsString("version conflict, current version [1] is " + + "different than the one provided [1000]")); // <5> + // end::multi-get-conflict + } + + } + + private MultiGetItemResponse unwrapAndAssertExample(MultiGetResponse response) { + assertThat(response.getResponses(), arrayWithSize(1)); + MultiGetItemResponse item = response.getResponses()[0]; + assertEquals("index", item.getIndex()); + assertEquals("type", item.getType()); + assertEquals("example_id", item.getId()); + return item; + } } diff --git a/docs/java-rest/high-level/document/multi-get.asciidoc b/docs/java-rest/high-level/document/multi-get.asciidoc new file mode 100644 index 00000000000..1f4628e149c --- /dev/null +++ b/docs/java-rest/high-level/document/multi-get.asciidoc @@ -0,0 +1,168 @@ +[[java-rest-high-document-multi-get]] +=== Multi-Get API + +The `multiGet` API executes multiple <> +requests in a single http request in parallel. + +[[java-rest-high-document-mulit-get-request]] +==== Multi-Get Request + +A `MultiGetRequest` is built empty and you add `MultiGetRequest.Item`s to +configure what to fetch: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CRUDDocumentationIT.java[multi-get-request] +-------------------------------------------------- +<1> Index +<2> Type +<3> Document id +<4> Add another item to fetch + +==== Optional arguments + +`multiGet` supports the same optional arguments that the +<> supports. +You can set most of these on the `Item`: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CRUDDocumentationIT.java[multi-get-request-no-source] +-------------------------------------------------- +<1> Disable source retrieval, enabled by default + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CRUDDocumentationIT.java[multi-get-request-source-include] +-------------------------------------------------- +<1> Configure source inclusion for specific fields + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CRUDDocumentationIT.java[multi-get-request-source-exclude] +-------------------------------------------------- +<1> Configure source exclusion for specific fields + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CRUDDocumentationIT.java[multi-get-request-stored] +-------------------------------------------------- +<1> Configure retrieval for specific stored fields (requires fields to be +stored separately in the mappings) +<2> Retrieve the `foo` stored field (requires the field to be stored +separately in the mappings) + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CRUDDocumentationIT.java[multi-get-request-item-extras] +-------------------------------------------------- +<1> Routing value +<2> Parent value +<3> Version +<4> Version type + +{ref}/search-request-preference.html[`preference`], +{ref}/docs-get.html#realtime[`realtime`] +and +{ref}/docs-get.html#get-refresh[`refresh`] can be set on the main request but +not on any items: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CRUDDocumentationIT.java[multi-get-request-top-level-extras] +-------------------------------------------------- +<1> Preference value +<2> Set realtime flag to `false` (`true` by default) +<3> Perform a refresh before retrieving the document (`false` by default) + +[[java-rest-high-document-multi-get-sync]] +==== Synchronous Execution + +After building the `MultiGetRequest` you can execute it synchronously with +`multiGet`: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CRUDDocumentationIT.java[multi-get-execute] +-------------------------------------------------- + +[[java-rest-high-document-multi-get-async]] +==== Asynchronous Execution + +The asynchronous execution of a multi get request requires both the +`MultiGetRequest` instance and an `ActionListener` instance to be passed to +the asynchronous method: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CRUDDocumentationIT.java[multi-get-execute-async] +-------------------------------------------------- +<1> The `MultiGetRequest` to execute and the `ActionListener` to use when +the execution completes. + +The asynchronous method does not block and returns immediately. Once the +request completed the `ActionListener` is called back using the `onResponse` +method if the execution successfully completed or using the `onFailure` method +if it failed. + +A typical listener for `MultiGetResponse` looks like: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CRUDDocumentationIT.java[multi-get-execute-listener] +-------------------------------------------------- +<1> Called when the execution is successfully completed. The response is +provided as an argument. +<2> Called in case of failure. The raised exception is provided as an argument. + +[[java-rest-high-document-multi-get-response]] +==== Multi Get Response + +The returned `MultiGetResponse` contains a list of `MultiGetItemResponse`s in +`getResponses` in the same order that they were requested. +`MultiGetItemResponse` contains *either* a +<> if the get succeeded +or a `MultiGetResponse.Failure` if it failed. A success looks just like a +normal `GetResponse`. + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CRUDDocumentationIT.java[multi-get-response] +-------------------------------------------------- +<1> `getFailure` returns null because there isn't a failure. +<2> `getResponse` returns the `GetResponse`. +<3> Retrieve the document as a `String` +<4> Retrieve the document as a `Map` +<5> Retrieve the document as a `byte[]` +<6> Handle the scenario where the document was not found. Note that although +the returned response has `404` status code, a valid `GetResponse` is +returned rather than an exception thrown. Such response does not hold any +source document and its `isExists` method returns `false`. + +When one of the subrequests as performed against an index that does not exist +`getFailure` will contain an exception: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CRUDDocumentationIT.java[multi-get-indexnotfound] +-------------------------------------------------- +<1> `getResponse` is null. +<2> `getFailure` isn't and contains an `Exception`. +<3> That `Exception` is actually an `ElasticsearchException` +<4> and it has a status of `NOT_FOUND`. It'd have been an HTTP 404 if this +wasn't a multi get. +<5> `getMessage` explains the actual cause, `no such index`. + +In case a specific document version has been requested, and the existing +document has a different version number, a version conflict is raised: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CRUDDocumentationIT.java[multi-get-conflict] +-------------------------------------------------- +<1> `getResponse` is null. +<2> `getFailure` isn't and contains an `Exception`. +<3> That `Exception` is actuall and `ElasticsearchException` +<4> and it has a status of `CONFLICT`. It'd have been an HTTP 409 if this +wasn't a multi get. +<5> `getMessage` explains the actual cause, ` diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc index 79f17db5774..615634b65f1 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -14,6 +14,7 @@ Single document APIs:: [[multi-doc]] Multi-document APIs:: * <> +* <> include::document/index.asciidoc[] include::document/get.asciidoc[] @@ -21,6 +22,7 @@ include::document/exists.asciidoc[] include::document/delete.asciidoc[] include::document/update.asciidoc[] include::document/bulk.asciidoc[] +include::document/multi-get.asciidoc[] == Search APIs