Skip to content

Commit e97c88f

Browse files
committed
Check presence of multi-types before validating new mapping (#29316)
Before doing any kind of validation on a new mapping, we should first do the multi-type validation in order to provide better error messages. For #29313, this means that the exception message will be Rejecting mapping update to [range_index_new] as the final mapping would have more than 1 type: [_doc, mytype] instead of [expected_attendees] is defined as an object in mapping [mytype] but this name is already used for a field in other types
1 parent 77364fd commit e97c88f

File tree

4 files changed

+85
-28
lines changed

4 files changed

+85
-28
lines changed

server/src/main/java/org/elasticsearch/index/mapper/MapperService.java

+10-9
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,16 @@ private synchronized Map<String, DocumentMapper> internalMerge(@Nullable Documen
399399
results.put(DEFAULT_MAPPING, defaultMapper);
400400
}
401401

402+
if (indexSettings.isSingleType()) {
403+
Set<String> actualTypes = new HashSet<>(mappers.keySet());
404+
documentMappers.forEach(mapper -> actualTypes.add(mapper.type()));
405+
actualTypes.remove(DEFAULT_MAPPING);
406+
if (actualTypes.size() > 1) {
407+
throw new IllegalArgumentException(
408+
"Rejecting mapping update to [" + index().getName() + "] as the final mapping would have more than 1 type: " + actualTypes);
409+
}
410+
}
411+
402412
for (DocumentMapper mapper : documentMappers) {
403413
// check naming
404414
validateTypeName(mapper.type());
@@ -496,15 +506,6 @@ private synchronized Map<String, DocumentMapper> internalMerge(@Nullable Documen
496506
}
497507
}
498508

499-
if (indexSettings.isSingleType()) {
500-
Set<String> actualTypes = new HashSet<>(mappers.keySet());
501-
actualTypes.remove(DEFAULT_MAPPING);
502-
if (actualTypes.size() > 1) {
503-
throw new IllegalArgumentException(
504-
"Rejecting mapping update to [" + index().getName() + "] as the final mapping would have more than 1 type: " + actualTypes);
505-
}
506-
}
507-
508509
// make structures immutable
509510
mappers = Collections.unmodifiableMap(mappers);
510511
results = Collections.unmodifiableMap(results);

server/src/test/java/org/elasticsearch/action/admin/indices/create/CreateIndexIT.java

+14-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import com.carrotsearch.hppc.cursors.ObjectCursor;
2323
import org.elasticsearch.ElasticsearchException;
24+
import org.elasticsearch.Version;
2425
import org.elasticsearch.action.ActionListener;
2526
import org.elasticsearch.action.UnavailableShardsException;
2627
import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse;
@@ -38,11 +39,16 @@
3839
import org.elasticsearch.env.NodeEnvironment;
3940
import org.elasticsearch.index.IndexNotFoundException;
4041
import org.elasticsearch.index.query.RangeQueryBuilder;
42+
import org.elasticsearch.plugins.Plugin;
4143
import org.elasticsearch.test.ESIntegTestCase;
4244
import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
4345
import org.elasticsearch.test.ESIntegTestCase.Scope;
46+
import org.elasticsearch.test.InternalSettingsPlugin;
4447
import org.elasticsearch.test.InternalTestCluster;
48+
import org.elasticsearch.test.VersionUtils;
4549

50+
import java.util.Arrays;
51+
import java.util.Collection;
4652
import java.util.HashMap;
4753
import java.util.HashSet;
4854
import java.util.Set;
@@ -66,6 +72,11 @@
6672
@ClusterScope(scope = Scope.TEST)
6773
public class CreateIndexIT extends ESIntegTestCase {
6874

75+
@Override
76+
protected Collection<Class<? extends Plugin>> nodePlugins() {
77+
return Arrays.asList(InternalSettingsPlugin.class); // uses index.version.created
78+
}
79+
6980
public void testCreationDateGivenFails() {
7081
try {
7182
prepareCreate("test").setSettings(Settings.builder().put(IndexMetaData.SETTING_CREATION_DATE, 4L)).get();
@@ -268,7 +279,9 @@ public void onFailure(Exception e) {
268279
* Asserts that the root cause of mapping conflicts is readable.
269280
*/
270281
public void testMappingConflictRootCause() throws Exception {
271-
CreateIndexRequestBuilder b = prepareCreate("test");
282+
CreateIndexRequestBuilder b = prepareCreate("test")
283+
.setSettings(Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED,
284+
VersionUtils.randomVersionBetween(random(), Version.V_5_0_0, Version.V_5_6_9)));
272285
b.addMapping("type1", jsonBuilder().startObject().startObject("properties")
273286
.startObject("text")
274287
.field("type", "text")

server/src/test/java/org/elasticsearch/index/mapper/MapperServiceTests.java

+18-2
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ public void testMappingDepthExceedsLimit() throws Throwable {
182182
indexService2.mapperService().merge("type", objectMapping, MergeReason.MAPPING_UPDATE, false);
183183

184184
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
185-
() -> indexService1.mapperService().merge("type2", objectMapping, MergeReason.MAPPING_UPDATE, false));
185+
() -> indexService1.mapperService().merge("type", objectMapping, MergeReason.MAPPING_UPDATE, false));
186186
assertThat(e.getMessage(), containsString("Limit of mapping depth [1] in index [test1] has been exceeded"));
187187
}
188188

@@ -290,7 +290,6 @@ public void testPartitionedConstraints() {
290290
// partitioned index cannot have parent/child relationships
291291
IllegalArgumentException parentException = expectThrows(IllegalArgumentException.class, () -> {
292292
client().admin().indices().prepareCreate("test-index")
293-
.addMapping("parent", "{\"parent\":{\"_routing\":{\"required\":true}}}", XContentType.JSON)
294293
.addMapping("child", "{\"child\": {\"_routing\":{\"required\":true}, \"_parent\": {\"type\": \"parent\"}}}",
295294
XContentType.JSON)
296295
.setSettings(Settings.builder()
@@ -342,6 +341,23 @@ public void testForbidMultipleTypes() throws IOException {
342341
assertThat(e.getMessage(), Matchers.startsWith("Rejecting mapping update to [test] as the final mapping would have more than 1 type: "));
343342
}
344343

344+
/**
345+
* This test checks that the multi-type validation is done before we do any other kind of validation on the mapping that's added,
346+
* see https://github.com/elastic/elasticsearch/issues/29313
347+
*/
348+
public void testForbidMultipleTypesWithConflictingMappings() throws IOException {
349+
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type")
350+
.startObject("properties").startObject("field1").field("type", "integer_range").endObject().endObject().endObject().endObject());
351+
MapperService mapperService = createIndex("test").mapperService();
352+
mapperService.merge("type", new CompressedXContent(mapping), MergeReason.MAPPING_UPDATE, false);
353+
354+
String mapping2 = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type2")
355+
.startObject("properties").startObject("field1").field("type", "integer").endObject().endObject().endObject().endObject());
356+
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
357+
() -> mapperService.merge("type2", new CompressedXContent(mapping2), MergeReason.MAPPING_UPDATE, false));
358+
assertThat(e.getMessage(), Matchers.startsWith("Rejecting mapping update to [test] as the final mapping would have more than 1 type: "));
359+
}
360+
345361
public void testDefaultMappingIsDeprecated() throws IOException {
346362
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("_default_").endObject().endObject());
347363
MapperService mapperService = createIndex("test").mapperService();

server/src/test/java/org/elasticsearch/index/mapper/UpdateMappingTests.java

+43-16
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ public void testConflictNewType() throws Exception {
120120
XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject("type1")
121121
.startObject("properties").startObject("foo").field("type", "long").endObject()
122122
.endObject().endObject().endObject();
123-
MapperService mapperService = createIndex("test", Settings.builder().build(), "type1", mapping).mapperService();
123+
MapperService mapperService = createIndex("test", Settings.builder().put("index.version.created",
124+
Version.V_5_6_0).build(), "type1", mapping).mapperService();
124125

125126
XContentBuilder update = XContentFactory.jsonBuilder().startObject().startObject("type2")
126127
.startObject("properties").startObject("foo").field("type", "double").endObject()
@@ -134,17 +135,8 @@ public void testConflictNewType() throws Exception {
134135
assertTrue(e.getMessage(), e.getMessage().contains("mapper [foo] cannot be changed from type [long] to [double]"));
135136
}
136137

137-
try {
138-
mapperService.merge("type2", new CompressedXContent(Strings.toString(update)), MapperService.MergeReason.MAPPING_UPDATE, false);
139-
fail();
140-
} catch (IllegalArgumentException e) {
141-
// expected
142-
assertTrue(e.getMessage(), e.getMessage().contains("mapper [foo] cannot be changed from type [long] to [double]"));
143-
}
144-
145138
assertThat(((FieldMapper) mapperService.documentMapper("type1").mapping().root().getMapper("foo")).fieldType().typeName(),
146139
equalTo("long"));
147-
assertNull(mapperService.documentMapper("type2"));
148140
}
149141

150142
// same as the testConflictNewType except that the mapping update is on an existing type
@@ -224,18 +216,53 @@ public void testRejectFieldDefinedTwice() throws IOException {
224216
.endObject()
225217
.endObject().endObject());
226218

227-
MapperService mapperService1 = createIndex("test1").mapperService();
219+
MapperService mapperService1 = createIndex("test1", Settings.builder().put("index.version.created",
220+
Version.V_5_6_0).build()).mapperService();
221+
228222
mapperService1.merge("type1", new CompressedXContent(mapping1), MergeReason.MAPPING_UPDATE, false);
229223
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
230-
() -> mapperService1.merge("type2", new CompressedXContent(mapping2), MergeReason.MAPPING_UPDATE, false));
224+
() -> mapperService1.merge("type2", new CompressedXContent(mapping2), MergeReason.MAPPING_UPDATE, false));
231225
assertThat(e.getMessage(), equalTo("[foo] is defined as a field in mapping [type2"
232-
+ "] but this name is already used for an object in other types"));
226+
+ "] but this name is already used for an object in other types"));
233227

234-
MapperService mapperService2 = createIndex("test2").mapperService();
228+
MapperService mapperService2 = createIndex("test2", Settings.builder().put("index.version.created",
229+
Version.V_5_6_0).build()).mapperService();
235230
mapperService2.merge("type2", new CompressedXContent(mapping2), MergeReason.MAPPING_UPDATE, false);
236231
e = expectThrows(IllegalArgumentException.class,
237-
() -> mapperService2.merge("type1", new CompressedXContent(mapping1), MergeReason.MAPPING_UPDATE, false));
232+
() -> mapperService2.merge("type1", new CompressedXContent(mapping1), MergeReason.MAPPING_UPDATE, false));
238233
assertThat(e.getMessage(), equalTo("[foo] is defined as an object in mapping [type1"
239-
+ "] but this name is already used for a field in other types"));
234+
+ "] but this name is already used for a field in other types"));
235+
}
236+
237+
public void testRejectFieldDefinedTwiceInSameType() throws IOException {
238+
String mapping1 = Strings.toString(XContentFactory.jsonBuilder().startObject()
239+
.startObject("type")
240+
.startObject("properties")
241+
.startObject("foo")
242+
.field("type", "object")
243+
.endObject()
244+
.endObject()
245+
.endObject().endObject());
246+
String mapping2 = Strings.toString(XContentFactory.jsonBuilder().startObject()
247+
.startObject("type")
248+
.startObject("properties")
249+
.startObject("foo")
250+
.field("type", "long")
251+
.endObject()
252+
.endObject()
253+
.endObject().endObject());
254+
255+
MapperService mapperService1 = createIndex("test1").mapperService();
256+
257+
mapperService1.merge("type", new CompressedXContent(mapping1), MergeReason.MAPPING_UPDATE, false);
258+
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
259+
() -> mapperService1.merge("type", new CompressedXContent(mapping2), MergeReason.MAPPING_UPDATE, false));
260+
assertThat(e.getMessage(), equalTo("Can't merge a non object mapping [foo] with an object mapping [foo]"));
261+
262+
MapperService mapperService2 = createIndex("test2").mapperService();
263+
mapperService2.merge("type", new CompressedXContent(mapping2), MergeReason.MAPPING_UPDATE, false);
264+
e = expectThrows(IllegalArgumentException.class,
265+
() -> mapperService2.merge("type", new CompressedXContent(mapping1), MergeReason.MAPPING_UPDATE, false));
266+
assertThat(e.getMessage(), equalTo("mapper [foo] of different type, current_type [long], merged_type [ObjectMapper]"));
240267
}
241268
}

0 commit comments

Comments
 (0)