Skip to content

Commit

Permalink
[REST Compatible API] Typed endpoints for Index and Get APIs (#69131)
Browse files Browse the repository at this point in the history
The types removal effort has removed the type from Index API in #47671 and from Get API in #46587
This commit allows to use 'typed' endpoints for the both Index and Get APIs

relates compatible types-removal meta issue #54160
  • Loading branch information
pgomulka authored Mar 23, 2021
1 parent edb4269 commit e942873
Show file tree
Hide file tree
Showing 12 changed files with 218 additions and 40 deletions.
40 changes: 10 additions & 30 deletions rest-api-spec/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -143,22 +143,9 @@ tasks.named("yamlRestCompatTest").configure {
'explain/40_mix_typeless_typeful/Explain with typeless API on an index that has types',
'field_caps/10_basic/Get date_nanos field caps',
'field_caps/30_filter/Field caps with index filter',
'get/100_mix_typeless_typeful/GET with typeless API on an index that has types',
'get/10_basic/Basic',
'get/11_basic_with_types/Basic',
'get/15_default_values/Default values',
'get/16_default_values_with_types/Default values',
'get/20_stored_fields/Stored fields',
'get/21_stored_fields_with_types/Stored fields',
'get/41_routing_with_types/Routing',
'get/50_with_headers/REST test with headers',
'get/51_with_headers_with_types/REST test with headers',
'get/61_realtime_refresh_with_types/Realtime Refresh',
'get/70_source_filtering/Source filtering',
'get/71_source_filtering_with_types/Source filtering',
'get/81_missing_with_types/Missing document with catch',
'get/81_missing_with_types/Missing document with ignore',
'get/91_versions_with_types/Versions',
'get/100_mix_typeless_typeful/GET with typeless API on an index that has types',// failing due to include_type_name #48632
'get/21_stored_fields_with_types/Stored fields', // failing due to include_type_name #48632
'get/71_source_filtering_with_types/Source filtering',// failing due to include_type_name #48632
'get_source/11_basic_with_types/Basic with types',
'get_source/16_default_values_with_types/Default values',
'get_source/41_routing_with_types/Routing',
Expand All @@ -168,20 +155,8 @@ tasks.named("yamlRestCompatTest").configure {
'get_source/81_missing_with_types/Missing document with ignore',
'get_source/86_source_missing_with_types/Missing document source with catch',
'get_source/86_source_missing_with_types/Missing document source with ignore',
'index/10_with_id/Index with ID',
'index/11_with_id_with_types/Index with ID',
'index/13_result_with_types/Index result field',
'index/15_without_id/Index without ID',
'index/16_without_id_with_types/Index without ID',
'index/21_optype_with_types/Optype',
'index/37_external_version_with_types/External version',
'index/38_external_gte_version_with_types/External GTE version',
'index/41_routing_with_types/Routing',
'index/61_refresh_with_types/Refresh',
'index/61_refresh_with_types/When refresh url parameter is an empty string that means "refresh immediately"',
'index/61_refresh_with_types/refresh=wait_for waits until changes are visible in search',
'index/70_mix_typeless_typeful/Index call that introduces new field mappings',
'index/70_mix_typeless_typeful/Index with typeless API on an index that has types',
'index/70_mix_typeless_typeful/Index call that introduces new field mappings', // failing due to include_type_name #48632
'index/70_mix_typeless_typeful/Index with typeless API on an index that has types', // failing due to include_type_name #48632
'indices.clone/10_basic/Clone index via API',
'indices.create/10_basic/Create index with explicit _doc type',
'indices.create/10_basic/Create index without soft deletes',
Expand Down Expand Up @@ -376,3 +351,8 @@ tasks.named("yamlRestCompatTest").configure {
'update/90_mix_typeless_typeful/Update with typeless API on an index that has types'
].join(',')
}

tasks.named("transformV7RestTests").configure({ task ->
task.replaceMatch("_type", "_doc")
task.addAllowedWarningRegex("\\[types removal\\].*")
})
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.elasticsearch.action.support.replication.ReplicationResponse;
import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.RestApiVersion;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
Expand Down Expand Up @@ -293,6 +294,9 @@ private void writeWithoutShardId(StreamOutput out) throws IOException {
@Override
public final XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
if (builder.getRestApiVersion() == RestApiVersion.V_7) {
builder.field(MapperService.TYPE_FIELD_NAME, MapperService.SINGLE_MAPPING_NAME);
}
innerToXContent(builder, params);
builder.endObject();
return builder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.Version;
import org.elasticsearch.common.RestApiVersion;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.compress.CompressorFactory;
Expand Down Expand Up @@ -282,6 +283,9 @@ public XContentBuilder toXContentEmbedded(XContentBuilder builder, Params params
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(_INDEX, index);
if (builder.getRestApiVersion() == RestApiVersion.V_7) {
builder.field(MapperService.TYPE_FIELD_NAME, MapperService.SINGLE_MAPPING_NAME);
}
builder.field(_ID, id);
if (isExists()) {
if (version != -1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public enum MergeReason {
}

public static final String SINGLE_MAPPING_NAME = "_doc";
public static final String TYPE_FIELD_NAME = "_type";
public static final Setting<Long> INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING =
Setting.longSetting("index.mapping.nested_fields.limit", 50L, 0, Property.Dynamic, Property.IndexScope);
// maximum allowed number of nested json objects across all fields in a single document
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.RestApiVersion;
import org.elasticsearch.common.Strings;
import org.elasticsearch.index.VersionType;
import org.elasticsearch.rest.BaseRestHandler;
Expand All @@ -29,6 +30,8 @@
import static org.elasticsearch.rest.RestStatus.OK;

public class RestGetAction extends BaseRestHandler {
static final String TYPES_DEPRECATION_MESSAGE = "[types removal] Specifying types in "
+ "document get requests is deprecated, use the /{index}/_doc/{id} endpoint instead.";

@Override
public String getName() {
Expand All @@ -39,11 +42,21 @@ public String getName() {
public List<Route> routes() {
return List.of(
new Route(GET, "/{index}/_doc/{id}"),
new Route(HEAD, "/{index}/_doc/{id}"));
new Route(HEAD, "/{index}/_doc/{id}"),
Route.builder(GET, "/{index}/{type}/{id}")
.deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7)
.build(),
Route.builder(HEAD, "/{index}/{type}/{id}")
.deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7)
.build());
}

@Override
public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
if (request.getRestApiVersion() == RestApiVersion.V_7) {
request.param("type"); // consume and ignore the type
}

GetRequest getRequest = new GetRequest(request.param("index"), request.param("id"));

getRequest.refresh(request.paramAsBoolean("refresh", getRequest.refresh()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.elasticsearch.action.support.ActiveShardCount;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.common.RestApiVersion;
import org.elasticsearch.index.VersionType;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestRequest;
Expand All @@ -29,12 +30,21 @@
import static org.elasticsearch.rest.RestRequest.Method.PUT;

public class RestIndexAction extends BaseRestHandler {
static final String TYPES_DEPRECATION_MESSAGE = "[types removal] Specifying types in document "
+ "index requests is deprecated, use the typeless endpoints instead (/{index}/_doc/{id}, /{index}/_doc, "
+ "or /{index}/_create/{id}).";

@Override
public List<Route> routes() {
return List.of(
new Route(POST, "/{index}/_doc/{id}"),
new Route(PUT, "/{index}/_doc/{id}"));
new Route(PUT, "/{index}/_doc/{id}"),
Route.builder(POST, "/{index}/{type}/{id}")
.deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7)
.build(),
Route.builder(PUT, "/{index}/{type}/{id}")
.deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7)
.build());
}

@Override
Expand All @@ -53,7 +63,13 @@ public String getName() {
public List<Route> routes() {
return List.of(
new Route(POST, "/{index}/_create/{id}"),
new Route(PUT, "/{index}/_create/{id}"));
new Route(PUT, "/{index}/_create/{id}"),
Route.builder(POST, "/{index}/{type}/{id}/_create")
.deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7)
.build(),
Route.builder(PUT, "/{index}/{type}/{id}/_create")
.deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7)
.build());
}

@Override
Expand Down Expand Up @@ -85,7 +101,11 @@ public String getName() {

@Override
public List<Route> routes() {
return List.of(new Route(POST, "/{index}/_doc"));
return List.of(
new Route(POST, "/{index}/_doc"),
Route.builder(POST, "/{index}/{type}")
.deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7)
.build());
}

@Override
Expand All @@ -101,6 +121,10 @@ public RestChannelConsumer prepareRequest(RestRequest request, final NodeClient

@Override
public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
if (request.getRestApiVersion() == RestApiVersion.V_7) {
request.param("type"); // consume and ignore the type
}

IndexRequest indexRequest = new IndexRequest(request.param("index"));
indexRequest.id(request.param("id"));
indexRequest.routing(request.param("routing"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@

import org.elasticsearch.action.DocWriteResponse.Result;
import org.elasticsearch.action.support.replication.ReplicationResponse.ShardInfo;
import org.elasticsearch.common.RestApiVersion;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.seqno.SequenceNumbers;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.test.ESTestCase;
Expand Down Expand Up @@ -96,4 +98,35 @@ public void testToXContentDoesntIncludeForcedRefreshUnlessForced() throws IOExce
}
}
}

public void testTypeWhenCompatible() throws IOException {
DocWriteResponse response = new DocWriteResponse(
new ShardId("index", "uuid", 0),
"id",
SequenceNumbers.UNASSIGNED_SEQ_NO,
17,
0,
DocWriteResponse.Result.CREATED
) {
// DocWriteResponse is abstract so we have to sneak a subclass in here to test it.
};
try (XContentBuilder builder = JsonXContent.contentBuilder()) {
builder.withCompatibleVersion(RestApiVersion.V_7);
response.toXContent(builder, ToXContent.EMPTY_PARAMS);

try (XContentParser parser = createParser(JsonXContent.jsonXContent, BytesReference.bytes(builder))) {
assertThat(parser.map(), hasEntry(MapperService.TYPE_FIELD_NAME, MapperService.SINGLE_MAPPING_NAME));
}
}

try (XContentBuilder builder = JsonXContent.contentBuilder()) {
builder.withCompatibleVersion(RestApiVersion.V_8);
response.toXContent(builder, ToXContent.EMPTY_PARAMS);

try (XContentParser parser = createParser(JsonXContent.jsonXContent, BytesReference.bytes(builder))) {
assertThat(parser.map(), not(hasKey(MapperService.TYPE_FIELD_NAME)));
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@

package org.elasticsearch.index.get;

import org.elasticsearch.common.RestApiVersion;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.document.DocumentField;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.index.mapper.IdFieldMapper;
import org.elasticsearch.index.mapper.IndexFieldMapper;
import org.elasticsearch.index.mapper.SeqNoFieldMapper;
Expand Down Expand Up @@ -86,6 +89,34 @@ public void testToXContent() throws IOException {
}
}

public void testToCompatibleXContent() throws IOException {
{
GetResult getResult = new GetResult("index", "id", 0, 1, 1, true, new BytesArray("{ \"field1\" : " +
"\"value1\", \"field2\":\"value2\"}"), singletonMap("field1", new DocumentField("field1",
singletonList("value1"))), singletonMap("field1", new DocumentField("metafield",
singletonList("metavalue"))));

try (XContentBuilder builder = JsonXContent.contentBuilder()) {
builder.withCompatibleVersion(RestApiVersion.V_7);
getResult.toXContent(builder, ToXContent.EMPTY_PARAMS);
String output = Strings.toString(builder);
assertEquals("{\"_index\":\"index\",\"_type\":\"_doc\",\"_id\":\"id\",\"_version\":1,\"_seq_no\":0,\"_primary_term\":1," +
"\"metafield\":\"metavalue\",\"found\":true,\"_source\":{ \"field1\" : \"value1\", \"field2\":\"value2\"}," +
"\"fields\":{\"field1\":[\"value1\"]}}", output);
}
}
{
GetResult getResult = new GetResult("index", "id", UNASSIGNED_SEQ_NO, 0, 1, false, null, null, null);

try (XContentBuilder builder = JsonXContent.contentBuilder()) {
builder.withCompatibleVersion(RestApiVersion.V_7);
getResult.toXContent(builder, ToXContent.EMPTY_PARAMS);
String output = Strings.toString(builder);
assertEquals("{\"_index\":\"index\",\"_type\":\"_doc\",\"_id\":\"id\",\"found\":false}", output);
}
}
}

public void testToAndFromXContentEmbedded() throws Exception {
XContentType xContentType = randomFrom(XContentType.values());
Tuple<GetResult, GetResult> tuple = randomGetResult(xContentType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

package org.elasticsearch.rest;

import org.elasticsearch.Version;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.RestApiVersion;
import org.elasticsearch.common.breaker.CircuitBreaker;
Expand Down Expand Up @@ -630,7 +629,7 @@ public void testDispatchCompatibleHandler() {

RestController restController = new RestController(Collections.emptySet(), null, client, circuitBreakerService, usageService);

final byte version = RestApiVersion.minimumSupported().major;
final RestApiVersion version = RestApiVersion.minimumSupported();

final String mediaType = randomCompatibleMediaType(version);
FakeRestRequest fakeRestRequest = requestWithContent(mediaType);
Expand All @@ -654,7 +653,7 @@ public void testDispatchCompatibleRequestToNewlyAddedHandler() {

RestController restController = new RestController(Collections.emptySet(), null, client, circuitBreakerService, usageService);

final byte version = RestApiVersion.minimumSupported().major;
final RestApiVersion version = RestApiVersion.minimumSupported();

final String mediaType = randomCompatibleMediaType(version);
FakeRestRequest fakeRestRequest = requestWithContent(mediaType);
Expand Down Expand Up @@ -690,7 +689,7 @@ private FakeRestRequest requestWithContent(String mediaType) {
public void testCurrentVersionVNDMediaTypeIsNotUsingCompatibility() {
RestController restController = new RestController(Collections.emptySet(), null, client, circuitBreakerService, usageService);

final byte version = Version.CURRENT.major;
final RestApiVersion version = RestApiVersion.current();

final String mediaType = randomCompatibleMediaType(version);
FakeRestRequest fakeRestRequest = requestWithContent(mediaType);
Expand Down
Loading

0 comments on commit e942873

Please sign in to comment.