Skip to content

Commit a536fa7

Browse files
authored
Treat put-mapping calls with _doc as a top-level key as typed calls. (#38032)
Currently the put-mapping API assumes that because the type name is `_doc` then it is dealing with a typeless put-mapping call. Yet we still allow running the put-mapping API in a typed fashion with `_doc` as a type name. The current logic triggers surprising errors when doing a typed put-mapping call with `_doc` as a type name on an index that has a type already. This is a bit of a corner-case, but is more important on 6.x due to the fact that using the index API with `_doc` as a type name triggers typed calls to the put-mapping API with `_doc` as a type name.
1 parent 3c439d3 commit a536fa7

File tree

2 files changed

+54
-6
lines changed

2 files changed

+54
-6
lines changed

rest-api-spec/src/main/resources/rest-api-spec/test/indices.put_mapping/20_mix_typeless_typeful.yml

+34-1
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,45 @@
3939
type: "keyword" # also test no-op updates that trigger special logic wrt the mapping version
4040

4141
- do:
42-
catch: bad_request
42+
catch: /the final mapping would have more than 1 type/
4343
indices.put_mapping:
4444
include_type_name: true
4545
index: index
46+
type: some_other_type
4647
body:
4748
some_other_type:
4849
properties:
4950
bar:
5051
type: "long"
52+
53+
54+
---
55+
"PUT mapping with _doc on an index that has types":
56+
57+
- skip:
58+
version: " - 6.99.99"
59+
reason: Backport first
60+
61+
62+
- do:
63+
indices.create:
64+
include_type_name: true
65+
index: index
66+
body:
67+
mappings:
68+
my_type:
69+
properties:
70+
foo:
71+
type: "keyword"
72+
73+
- do:
74+
catch: /the final mapping would have more than 1 type/
75+
indices.put_mapping:
76+
include_type_name: true
77+
index: index
78+
type: _doc
79+
body:
80+
_doc:
81+
properties:
82+
bar:
83+
type: "long"

server/src/main/java/org/elasticsearch/cluster/metadata/MetaDataMappingService.java

+20-5
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
import org.elasticsearch.common.compress.CompressedXContent;
3838
import org.elasticsearch.common.inject.Inject;
3939
import org.elasticsearch.common.unit.TimeValue;
40+
import org.elasticsearch.common.xcontent.XContentHelper;
41+
import org.elasticsearch.common.xcontent.XContentType;
4042
import org.elasticsearch.index.Index;
4143
import org.elasticsearch.index.IndexService;
4244
import org.elasticsearch.index.mapper.DocumentMapper;
@@ -277,7 +279,8 @@ private ClusterState applyRequest(ClusterState currentState, PutMappingClusterSt
277279
if (mappingType == null) {
278280
mappingType = newMapper.type();
279281
} else if (mappingType.equals(newMapper.type()) == false
280-
&& mapperService.resolveDocumentType(mappingType).equals(newMapper.type()) == false) {
282+
&& (isMappingSourceTyped(mapperService, mappingUpdateSource, request.type())
283+
|| mapperService.resolveDocumentType(mappingType).equals(newMapper.type()) == false)) {
281284
throw new InvalidTypeNameException("Type name provided does not match type name within mapping definition.");
282285
}
283286
}
@@ -297,10 +300,13 @@ private ClusterState applyRequest(ClusterState currentState, PutMappingClusterSt
297300
final Index index = indexMetaData.getIndex();
298301
final MapperService mapperService = indexMapperServices.get(index);
299302

300-
// If the user gave _doc as a special type value or if they are using the new typeless APIs,
301-
// then we apply the mapping update to the existing type. This allows to move to typeless
302-
// APIs with indices whose type name is different from `_doc`.
303-
String typeForUpdate = mapperService.resolveDocumentType(mappingType); // the type to use to apply the mapping update
303+
// If the _type name is _doc and there is no _doc top-level key then this means that we
304+
// are handling a typeless call. In such a case, we override _doc with the actual type
305+
// name in the mappings. This allows to use typeless APIs on typed indices.
306+
String typeForUpdate = mappingType; // the type to use to apply the mapping update
307+
if (isMappingSourceTyped(mapperService, mappingUpdateSource, request.type()) == false) {
308+
typeForUpdate = mapperService.resolveDocumentType(mappingType);
309+
}
304310

305311
CompressedXContent existingSource = null;
306312
DocumentMapper existingMapper = mapperService.documentMapper(typeForUpdate);
@@ -365,6 +371,15 @@ public String describeTasks(List<PutMappingClusterStateUpdateRequest> tasks) {
365371
}
366372
}
367373

374+
/**
375+
* Returns {@code true} if the given {@code mappingSource} includes a type
376+
* as a top-level object.
377+
*/
378+
private static boolean isMappingSourceTyped(MapperService mapperService, CompressedXContent mappingSource, String type) {
379+
Map<String, Object> root = XContentHelper.convertToMap(mappingSource.compressedReference(), true, XContentType.JSON).v2();
380+
return root.size() == 1 && root.keySet().iterator().next().equals(type);
381+
}
382+
368383
public void putMapping(final PutMappingClusterStateUpdateRequest request, final ActionListener<ClusterStateUpdateResponse> listener) {
369384
clusterService.submitStateUpdateTask("put-mapping",
370385
request,

0 commit comments

Comments
 (0)