diff --git a/rest-api-spec/build.gradle b/rest-api-spec/build.gradle index bc79a1015a8cc..d00b0d6cb3969 100644 --- a/rest-api-spec/build.gradle +++ b/rest-api-spec/build.gradle @@ -144,60 +144,29 @@ tasks.named("yamlRestCompatTest").configure { 'get_source/86_source_missing_with_types/Missing document source with catch', 'get_source/86_source_missing_with_types/Missing document source with ignore', 'indices.create/10_basic/Create index without soft deletes', - // 5 below await retrofitting Removes typed URLs from mapping APIs #41676 - 'indices.create/11_basic_with_types/Create index with mappings', - 'indices.create/20_mix_typeless_typeful/Create a typed index while there is a typeless template', - 'indices.create/20_mix_typeless_typeful/Create a typeless index while there is a typed template', + // type information about the type is removed and not passed down. The logic to check for this is also removed. 'indices.create/20_mix_typeless_typeful/Implicitly create a typed index while there is a typeless template', 'indices.create/20_mix_typeless_typeful/Implicitly create a typeless index while there is a typed template', // 'indices.flush/10_basic/Index synced flush rest test', 'indices.forcemerge/10_basic/Check deprecation warning when incompatible only_expunge_deletes and max_num_segments values are both set', - 'indices.get_field_mapping/10_basic/Get field mapping with local is deprecated',// awaits #41676 - 'indices.get_field_mapping/11_basic_with_types/Get field mapping by index only',// awaits #41676 - 'indices.get_field_mapping/11_basic_with_types/Get field mapping by type & field',// awaits #41676 - 'indices.get_field_mapping/11_basic_with_types/Get field mapping by type & field, with another field that doesn\'t exist',// awaits #41676 - 'indices.get_field_mapping/11_basic_with_types/Get field mapping should work without index specifying type and fields',// awaits #41676 - 'indices.get_field_mapping/11_basic_with_types/Get field mapping with include_defaults',// awaits #41676 - 'indices.get_field_mapping/11_basic_with_types/Get field mapping with no index and type',// awaits #41676 - 'indices.get_field_mapping/21_missing_field_with_types/Return empty object if field doesn\'t exist, but type and index do',// awaits #41676 - 'indices.get_field_mapping/30_missing_type/Raise 404 when type doesn\'t exist',// awaits #41676 - 'indices.get_field_mapping/51_field_wildcards_with_types/Get field mapping should work using \'*\' for indices and types',// awaits #41676 - 'indices.get_field_mapping/51_field_wildcards_with_types/Get field mapping should work using \'_all\' for indices and types',// awaits #41676 - 'indices.get_field_mapping/51_field_wildcards_with_types/Get field mapping should work using comma_separated values for indices and types',// awaits #41676 - 'indices.get_field_mapping/51_field_wildcards_with_types/Get field mapping with * for fields',// awaits #41676 - 'indices.get_field_mapping/51_field_wildcards_with_types/Get field mapping with *t1 for fields',// awaits #41676 - 'indices.get_field_mapping/51_field_wildcards_with_types/Get field mapping with t* for fields',// awaits #41676 - 'indices.get_field_mapping/51_field_wildcards_with_types/Get field mapping with wildcarded relative names',// awaits #41676 - 'indices.get_mapping/11_basic_with_types/Get /*/_mapping/{type}', - 'indices.get_mapping/11_basic_with_types/Get /_all/_mapping/{type}', - 'indices.get_mapping/11_basic_with_types/Get /_mapping', - 'indices.get_mapping/11_basic_with_types/Get /_mapping/{type}', - 'indices.get_mapping/11_basic_with_types/Get /index*/_mapping/{type}', - 'indices.get_mapping/11_basic_with_types/Get /index,index/_mapping/{type}', - 'indices.get_mapping/11_basic_with_types/Get /{index}/_mapping', - 'indices.get_mapping/11_basic_with_types/Get /{index}/_mapping/*', - 'indices.get_mapping/11_basic_with_types/Get /{index}/_mapping/_all', - 'indices.get_mapping/11_basic_with_types/Get /{index}/_mapping/{type*}', - 'indices.get_mapping/11_basic_with_types/Get /{index}/_mapping/{type}', + // This test returns test_index.mappings:{} when {} was expected. difference between 20_missing_field and 21_missing_field_with_types? + 'indices.get_field_mapping/21_missing_field_with_types/Return empty object if field doesn\'t exist, but type and index do', + // The information about the type is not present in the index. hence it cannot know if the type exist or not. + 'indices.get_field_mapping/30_missing_type/Raise 404 when type doesn\'t exist', + // The information about the type is not present in the index. hence it cannot know if the type exist or not. 'indices.get_mapping/20_missing_type/Existent and non-existent type returns 404 and the existing type', 'indices.get_mapping/20_missing_type/Existent and non-existent types returns 404 and the existing type', 'indices.get_mapping/20_missing_type/No type matching pattern returns 404', 'indices.get_mapping/20_missing_type/Non-existent type returns 404', 'indices.get_mapping/20_missing_type/Type missing when no types exist', + // 'indices.open/10_basic/?wait_for_active_shards default is deprecated', 'indices.open/10_basic/?wait_for_active_shards=index-setting', - 'indices.put_mapping/10_basic/Put mappings with explicit _doc type', - 'indices.put_mapping/11_basic_with_types/Test Create and update mapping', + + // The information about the type is not present in the index. hence it cannot know if the type was already used or not 'indices.put_mapping/20_mix_typeless_typeful/PUT mapping with _doc on an index that has types', 'indices.put_mapping/20_mix_typeless_typeful/PUT mapping with typeless API on an index that has types', - 'indices.put_mapping/all_path_options_with_types/post a mapping with default analyzer twice', - 'indices.put_mapping/all_path_options_with_types/put mapping in * index', - 'indices.put_mapping/all_path_options_with_types/put mapping in _all index', - 'indices.put_mapping/all_path_options_with_types/put mapping in list of indices', - 'indices.put_mapping/all_path_options_with_types/put mapping in prefix* index', - 'indices.put_mapping/all_path_options_with_types/put mapping with blank index', - 'indices.put_mapping/all_path_options_with_types/put one mapping per index', // there is a small distinction between empty mappings and no mappings at all. The code to implement this test was refactored #54003 // not fixing this in #70966 'indices.put_template/11_basic_with_types/Put template with empty mappings', @@ -297,8 +266,94 @@ tasks.named("transformV7RestTests").configure({ task -> task.replaceValueInMatch("nodes.\$node_id.roles.9", "remote_cluster_client", "node_info role test") task.removeMatch("nodes.\$node_id.roles.10", "node_info role test") task.replaceIsTrue("test_index.mappings.type_1", "test_index.mappings._doc") - task.replaceIsFalse("test_index.mappings.type_1", "test_index.mappings._doc") - task.replaceIsFalse("test-1.mappings.my_type", "test-1.mappings._doc") + //override for indices.get and indices.create + //task.replaceIsFalse("test_index.mappings.type_1", "test_index.mappings._doc") + //overrides for indices.create/20_mix_typeless_typeful + task.replaceIsFalse("test-1.mappings._doc","false", "Create a typed index while there is a typeless template") + task.replaceIsFalse("test-1.mappings._doc","false", "Create a typeless index while there is a typed template") + + task.replaceIsTrue("test-1.mappings.my_type", "test-1.mappings._doc") + task.replaceIsTrue("test-1.mappings.my_type.properties.foo", "test-1.mappings._doc.properties.foo") + task.replaceIsTrue("test-1.mappings.my_type.properties.bar", "test-1.mappings._doc.properties.bar") + + // overrides for indices.get_field_mapping + task.replaceKeyInLength("test_index.mappings.test_type.text.mapping.text.type", + "test_index.mappings._doc.text.mapping.text.type" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.text.mapping.text.analyzer", + "test_index.mappings._doc.text.mapping.text.analyzer" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.t1.full_name", + "test_index.mappings._doc.t1.full_name" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.t2.full_name", + "test_index.mappings._doc.t2.full_name" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.obj\\.t1.full_name", + "test_index.mappings._doc.obj\\.t1.full_name" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.obj\\.i_t1.full_name", + "test_index.mappings._doc.obj\\.i_t1.full_name" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.obj\\.i_t3.full_name", + "test_index.mappings._doc.obj\\.i_t3.full_name" + ) + task.replaceKeyInLength("test_index.mappings.test_type", + "test_index.mappings._doc" + ) + task.replaceKeyInMatch("test_index_2.mappings.test_type_2.t1.full_name", + "test_index.mappings._doc.t1.full_name" + ) + task.replaceKeyInMatch("test_index_2.mappings.test_type_2.t2.full_name", + "test_index.mappings._doc.t2.full_name" + ) + task.replaceKeyInLength("test_index_2.mappings.test_type_2", + "test_index.mappings._doc" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.text.mapping.text.type", + "test_index.mappings._doc.text.mapping.text.type" + ) + // overrides for indices.put_mapping/11_basic_with_types + task.replaceKeyInMatch("test_index.mappings.test_type.properties.text1.type", + "test_index.mappings._doc.properties.text1.type" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.properties.text1.analyzer", + "test_index.mappings._doc.properties.text1.analyzer" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.properties.text2.type", + "test_index.mappings._doc.properties.text2.type" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.properties.text2.analyzer", + "test_index.mappings._doc.properties.text2.analyzer" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.properties.subfield.properties.text3.type", + "test_index.mappings._doc.properties.subfield.properties.text3.type" + ) + task.replaceKeyInMatch("test_index.mappings.test_type.properties.text1.fields.text_raw.type", + "test_index.mappings._doc.properties.text1.fields.text_raw.type" + ) + // overrides for indices.put_mapping/all_path_options_with_types + task.replaceKeyInMatch("test_index1.mappings.test_type.properties.text.type", + "test_index1.mappings._doc.properties.text.type" + ) + task.replaceKeyInMatch("test_index1.mappings.test_type.properties.text.analyzer", + "test_index1.mappings._doc.properties.text.analyzer" + ) + task.replaceKeyInMatch("test_index2.mappings.test_type.properties.text.type", + "test_index2.mappings._doc.properties.text.type" + ) + task.replaceKeyInMatch("test_index2.mappings.test_type.properties.text.analyzer", + "test_index2.mappings._doc.properties.text.analyzer" + ) + task.replaceKeyInMatch("foo.mappings.test_type.properties.text.type", + "foo.mappings._doc.properties.text.type" + ) + task.replaceKeyInMatch("foo.mappings.test_type.properties.text.analyzer", + "foo.mappings._doc.properties.text.analyzer" + ) + // overrides for indices.get_mapping + task.replaceIsTrue("test_1.mappings.doc", "test_1.mappings._doc") + task.replaceIsTrue("test_2.mappings.doc", "test_2.mappings._doc") }) tasks.register('enforceYamlTestConvention').configure { diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/mapping/get/GetMappingsResponse.java b/server/src/main/java/org/elasticsearch/action/admin/indices/mapping/get/GetMappingsResponse.java index c7ed7ebaad560..20c543b0b698d 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/mapping/get/GetMappingsResponse.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/mapping/get/GetMappingsResponse.java @@ -13,6 +13,7 @@ import org.elasticsearch.action.ActionResponse; import org.elasticsearch.cluster.metadata.MappingMetadata; import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.RestApiVersion; import org.elasticsearch.common.Strings; import org.elasticsearch.common.collect.ImmutableOpenMap; import org.elasticsearch.common.io.stream.StreamInput; @@ -23,6 +24,9 @@ import java.io.IOException; +import static org.elasticsearch.rest.BaseRestHandler.DEFAULT_INCLUDE_TYPE_NAME_POLICY; +import static org.elasticsearch.rest.BaseRestHandler.INCLUDE_TYPE_NAME_PARAMETER; + public class GetMappingsResponse extends ActionResponse implements ToXContentFragment { private static final ParseField MAPPINGS = new ParseField("mappings"); @@ -65,7 +69,17 @@ public void writeTo(StreamOutput out) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { for (final ObjectObjectCursor indexEntry : getMappings()) { builder.startObject(indexEntry.key); - if (indexEntry.value != null) { + boolean includeTypeName = params.paramAsBoolean(INCLUDE_TYPE_NAME_PARAMETER, + DEFAULT_INCLUDE_TYPE_NAME_POLICY); + if (builder.getRestApiVersion() == RestApiVersion.V_7 && includeTypeName && indexEntry.value != null) { + builder.startObject(MAPPINGS.getPreferredName()); + + if (indexEntry.value != MappingMetadata.EMPTY_MAPPINGS) { + builder.field(MapperService.SINGLE_MAPPING_NAME, indexEntry.value.sourceAsMap()); + } + builder.endObject(); + + } else if (indexEntry.value != null) { builder.field(MAPPINGS.getPreferredName(), indexEntry.value.sourceAsMap()); } else { builder.startObject(MAPPINGS.getPreferredName()).endObject(); diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetFieldMappingAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetFieldMappingAction.java index 3c1ae91d42123..baa2178325ee9 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetFieldMappingAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetFieldMappingAction.java @@ -38,14 +38,21 @@ public class RestGetFieldMappingAction extends BaseRestHandler { private static final Logger logger = LogManager.getLogger(RestGetFieldMappingAction.class); private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(logger.getName()); - public static final String TYPES_DEPRECATION_MESSAGE = "[types removal] Using include_type_name in get " + + public static final String INCLUDE_TYPE_DEPRECATION_MESSAGE = "[types removal] Using include_type_name in get " + "field mapping requests is deprecated. The parameter will be removed in the next major version."; - + public static final String TYPES_DEPRECATION_MESSAGE = "[types removal] Specifying types in get field mapping request is deprecated. " + + "Use typeless api instead"; @Override public List routes() { return List.of( new Route(GET, "/_mapping/field/{fields}"), - new Route(GET, "/{index}/_mapping/field/{fields}")); + new Route(GET, "/{index}/_mapping/field/{fields}"), + Route.builder(GET, "/_mapping/{type}/field/{fields}") + .deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7).build(), + Route.builder(GET, "/{index}/{type}/_mapping/field/{fields}") + .deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7).build(), + Route.builder(GET, "/{index}/_mapping/{type}/field/{fields}") + .deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7).build()); } @Override @@ -58,9 +65,23 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC final String[] indices = Strings.splitStringByCommaToArray(request.param("index")); final String[] fields = Strings.splitStringByCommaToArray(request.param("fields")); - if (request.getRestApiVersion() == RestApiVersion.V_7 && request.hasParam(INCLUDE_TYPE_NAME_PARAMETER)) { - request.param(INCLUDE_TYPE_NAME_PARAMETER); - deprecationLogger.compatibleApiWarning("get_field_mapping_with_types", TYPES_DEPRECATION_MESSAGE); + if (request.getRestApiVersion() == RestApiVersion.V_7) { + if (request.hasParam(INCLUDE_TYPE_NAME_PARAMETER)) { + deprecationLogger.compatibleApiWarning("get_field_mapping_with_types", INCLUDE_TYPE_DEPRECATION_MESSAGE); + } + boolean includeTypeName = request.paramAsBoolean(INCLUDE_TYPE_NAME_PARAMETER, DEFAULT_INCLUDE_TYPE_NAME_POLICY); + final String[] types = request.paramAsStringArrayOrEmptyIfAll("type"); + if (includeTypeName == false && types.length > 0) { + throw new IllegalArgumentException("Types cannot be specified unless include_type_name" + " is set to true."); + } + + if (request.hasParam("local")) { + request.param("local"); + deprecationLogger.compatibleApiWarning( + "get_field_mapping_local", + "Use [local] in get field mapping requests is deprecated. " + "The parameter will be removed in the next major version" + ); + } } diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetMappingAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetMappingAction.java index cf3a95897c8e4..9b768beb40a5c 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetMappingAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetMappingAction.java @@ -31,11 +31,14 @@ import java.util.function.LongSupplier; import static org.elasticsearch.rest.RestRequest.Method.GET; +import static org.elasticsearch.rest.RestRequest.Method.HEAD; public class RestGetMappingAction extends BaseRestHandler { private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RestGetMappingAction.class); - public static final String TYPES_DEPRECATION_MESSAGE = "[types removal] Using include_type_name in get" + public static final String INCLUDE_TYPE_DEPRECATION_MSG = "[types removal] Using include_type_name in get" + " mapping requests is deprecated. The parameter will be removed in the next major version."; + public static final String TYPES_DEPRECATION_MESSAGE = "[types removal] Specifying types in get mapping request is deprecated. " + + "Use typeless api instead"; private final ThreadPool threadPool; @@ -48,8 +51,13 @@ public List routes() { return List.of( new Route(GET, "/_mapping"), new Route(GET, "/_mappings"), + Route.builder(GET, "/{index}/{type}/_mapping").deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7).build(), new Route(GET, "/{index}/_mapping"), - new Route(GET, "/{index}/_mappings")); + new Route(GET, "/{index}/_mappings"), + Route.builder(GET, "/{index}/_mappings/{type}").deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7).build(), + Route.builder(GET, "/{index}/_mapping/{type}").deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7).build(), + Route.builder(HEAD, "/{index}/_mapping/{type}").deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7).build(), + Route.builder(GET, "/_mapping/{type}").deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7).build()); } @Override @@ -59,11 +67,21 @@ public String getName() { @Override public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { - if (request.getRestApiVersion() == RestApiVersion.V_7 && request.hasParam(INCLUDE_TYPE_NAME_PARAMETER)) { - request.param(INCLUDE_TYPE_NAME_PARAMETER); - deprecationLogger.compatibleApiWarning("get_mapping_with_types", TYPES_DEPRECATION_MESSAGE); + if (request.getRestApiVersion() == RestApiVersion.V_7) { + if (request.hasParam(INCLUDE_TYPE_NAME_PARAMETER)) { + request.param(INCLUDE_TYPE_NAME_PARAMETER); + deprecationLogger.compatibleApiWarning("get_mapping_with_types", INCLUDE_TYPE_DEPRECATION_MSG); + } + final String[] types = request.paramAsStringArrayOrEmptyIfAll("type"); + if (request.paramAsBoolean(INCLUDE_TYPE_NAME_PARAMETER, DEFAULT_INCLUDE_TYPE_NAME_POLICY) == false && types.length > 0) { + throw new IllegalArgumentException("Types cannot be provided in get mapping requests, unless" + + " include_type_name is set to true."); + } + if (request.method().equals(HEAD)) { + deprecationLogger.compatibleApiWarning("get_mapping_types_removal", + "Type exists requests are deprecated, as types have been deprecated."); + } } - final String[] indices = Strings.splitStringByCommaToArray(request.param("index")); final GetMappingsRequest getMappingsRequest = new GetMappingsRequest(); diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestPutMappingAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestPutMappingAction.java index 3a63d9937dfe2..2879ad16cc984 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestPutMappingAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestPutMappingAction.java @@ -11,7 +11,9 @@ import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.RestApiVersion; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.logging.DeprecationLogger; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.rest.BaseRestHandler; @@ -23,10 +25,14 @@ import java.util.Map; import static org.elasticsearch.client.Requests.putMappingRequest; +import static org.elasticsearch.index.mapper.MapperService.isMappingSourceTyped; import static org.elasticsearch.rest.RestRequest.Method.POST; import static org.elasticsearch.rest.RestRequest.Method.PUT; public class RestPutMappingAction extends BaseRestHandler { + public static final String TYPES_DEPRECATION_MESSAGE = "[types removal] Specifying types in put mapping request is deprecated. " + + "Use typeless api instead"; + private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RestPutMappingAction.class); @Override public List routes() { @@ -34,7 +40,19 @@ public List routes() { new Route(POST, "/{index}/_mapping/"), new Route(PUT, "/{index}/_mapping/"), new Route(POST, "/{index}/_mappings/"), - new Route(PUT, "/{index}/_mappings/")); + new Route(PUT, "/{index}/_mappings/"), + Route.builder(POST, "/{index}/{type}/_mapping").deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7).build(), + Route.builder(PUT, "/{index}/{type}/_mapping").deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7).build(), + Route.builder(POST, "/{index}/_mapping/{type}").deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7).build(), + Route.builder(PUT, "/{index}/_mapping/{type}").deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7).build(), + Route.builder(POST, "/_mapping/{type}").deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7).build(), + Route.builder(PUT, "/_mapping/{type}").deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7).build(), + Route.builder(POST, "/{index}/{type}/_mappings").deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7).build(), + Route.builder(PUT, "/{index}/{type}/_mappings").deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7).build(), + Route.builder(POST, "/{index}/_mappings/{type}").deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7).build(), + Route.builder(PUT, "/{index}/_mappings/{type}").deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7).build(), + Route.builder(POST, "/_mappings/{type}").deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7).build(), + Route.builder(PUT, "/_mappings/{type}").deprecated(TYPES_DEPRECATION_MESSAGE, RestApiVersion.V_7).build()); } @Override @@ -48,15 +66,46 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC Map sourceAsMap = XContentHelper.convertToMap(request.requiredContent(), false, request.getXContentType()).v2(); - if (MapperService.isMappingSourceTyped(MapperService.SINGLE_MAPPING_NAME, sourceAsMap)) { - throw new IllegalArgumentException("Types cannot be provided in put mapping requests"); + if (request.getRestApiVersion() == RestApiVersion.V_7) { + final boolean includeTypeName = request.paramAsBoolean(INCLUDE_TYPE_NAME_PARAMETER, + DEFAULT_INCLUDE_TYPE_NAME_POLICY); + if (request.hasParam(INCLUDE_TYPE_NAME_PARAMETER)) { + deprecationLogger.compatibleApiWarning("put_mapping_with_types", TYPES_DEPRECATION_MESSAGE); + } + final String type = request.param("type"); + if (includeTypeName == false && + (type != null || isMappingSourceTyped(MapperService.SINGLE_MAPPING_NAME, sourceAsMap))) { + throw new IllegalArgumentException("Types cannot be provided in put mapping requests, unless " + + "the include_type_name parameter is set to true."); + } + + Map mappingSource = prepareV7Mappings(includeTypeName, sourceAsMap); + putMappingRequest.source(mappingSource); + } else { + if (MapperService.isMappingSourceTyped(MapperService.SINGLE_MAPPING_NAME, sourceAsMap)) { + throw new IllegalArgumentException("Types cannot be provided in put mapping requests"); + } + putMappingRequest.source(sourceAsMap); } - putMappingRequest.source(sourceAsMap); putMappingRequest.timeout(request.paramAsTime("timeout", putMappingRequest.timeout())); putMappingRequest.masterNodeTimeout(request.paramAsTime("master_timeout", putMappingRequest.masterNodeTimeout())); putMappingRequest.indicesOptions(IndicesOptions.fromRequest(request, putMappingRequest.indicesOptions())); putMappingRequest.writeIndexOnly(request.paramAsBoolean("write_index_only", false)); return channel -> client.admin().indices().putMapping(putMappingRequest, new RestToXContentListener<>(channel)); } + + private Map prepareV7Mappings(boolean includeTypeName, Map mappings) { + if (includeTypeName && mappings != null && mappings.size() == 1) { + String typeName = mappings.keySet().iterator().next(); + if (Strings.hasText(typeName) == false) { + throw new IllegalArgumentException("name cannot be empty string"); + } + @SuppressWarnings("unchecked") + Map typelessMappings = (Map) mappings.get(typeName); + return typelessMappings; + } + return mappings; + } + }