Skip to content

Commit

Permalink
Only allow one type on 7.0 indices (#24317)
Browse files Browse the repository at this point in the history
This adds the `index.mapping.single_type` setting, which enforces that indices
have at most one type when it is true. The default value is true for 6.0+ indices
and false for old indices.

Relates #15613
  • Loading branch information
jpountz authored Apr 27, 2017
1 parent 74acc59 commit 1be2800
Show file tree
Hide file tree
Showing 75 changed files with 580 additions and 373 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ public final class IndexScopedSettings extends AbstractScopedSettings {
MapperService.INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING,
MapperService.INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING,
MapperService.INDEX_MAPPING_DEPTH_LIMIT_SETTING,
MapperService.INDEX_MAPPING_SINGLE_TYPE_SETTING,
BitsetFilterCache.INDEX_LOAD_RANDOM_ACCESS_FILTERS_EAGERLY_SETTING,
IndexModule.INDEX_STORE_TYPE_SETTING,
IndexModule.INDEX_STORE_PRE_LOAD_SETTING,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.DelegatingAnalyzerWrapper;
import org.elasticsearch.ElasticsearchGenerationException;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.MappingMetaData;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.compress.CompressedXContent;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
Expand Down Expand Up @@ -94,6 +96,19 @@ public enum MergeReason {
public static final boolean INDEX_MAPPER_DYNAMIC_DEFAULT = true;
public static final Setting<Boolean> INDEX_MAPPER_DYNAMIC_SETTING =
Setting.boolSetting("index.mapper.dynamic", INDEX_MAPPER_DYNAMIC_DEFAULT, Property.Dynamic, Property.IndexScope);
public static final Setting<Boolean> INDEX_MAPPING_SINGLE_TYPE_SETTING;
static {
Function<Settings, String> defValue = settings -> {
// TODO: uncomment this
/*boolean singleType = true;
if (settings.getAsVersion(IndexMetaData.SETTING_VERSION_CREATED, null) != null) {
singleType = Version.indexCreated(settings).onOrAfter(Version.V_6_0_0_alpha1_UNRELEASED);
}*/
boolean singleType = false;
return Boolean.valueOf(singleType).toString();
};
INDEX_MAPPING_SINGLE_TYPE_SETTING = Setting.boolSetting("index.mapping.single_type", defValue, Property.IndexScope, Property.Final);
}
private static ObjectHashSet<String> META_FIELDS = ObjectHashSet.from(
"_uid", "_id", "_type", "_all", "_parent", "_routing", "_index",
"_size", "_timestamp", "_ttl"
Expand Down Expand Up @@ -457,6 +472,15 @@ private synchronized Map<String, DocumentMapper> internalMerge(@Nullable Documen
}
}

if (indexSettings.getValue(INDEX_MAPPING_SINGLE_TYPE_SETTING)) {
Set<String> actualTypes = new HashSet<>(mappers.keySet());
actualTypes.remove(DEFAULT_MAPPING);
if (actualTypes.size() > 1) {
throw new IllegalArgumentException(
"Rejecting mapping update to [" + index().getName() + "] as the final mapping would have more than 1 type: " + actualTypes);
}
}

// make structures immutable
mappers = Collections.unmodifiableMap(mappers);
results = Collections.unmodifiableMap(results);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public void testCreateShrinkIndex() {
.put("index.version.created", version)
).get();
for (int i = 0; i < 20; i++) {
client().prepareIndex("source", randomFrom("t1", "t2", "t3"))
client().prepareIndex("source", "type")
.setSource("{\"foo\" : \"bar\", \"i\" : " + i + "}", XContentType.JSON).get();
}
ImmutableOpenMap<String, DiscoveryNode> dataNodes = client().admin().cluster().prepareState().get().getState().nodes()
Expand Down Expand Up @@ -179,7 +179,7 @@ public void testCreateShrinkIndex() {
}

for (int i = 20; i < 40; i++) {
client().prepareIndex("target", randomFrom("t1", "t2", "t3"))
client().prepareIndex("target", "type")
.setSource("{\"foo\" : \"bar\", \"i\" : " + i + "}", XContentType.JSON).get();
}
flushAndRefresh();
Expand All @@ -197,7 +197,7 @@ public void testCreateShrinkIndexFails() throws Exception {
.put("number_of_shards", randomIntBetween(2, 7))
.put("number_of_replicas", 0)).get();
for (int i = 0; i < 20; i++) {
client().prepareIndex("source", randomFrom("t1", "t2", "t3"))
client().prepareIndex("source", "type")
.setSource("{\"foo\" : \"bar\", \"i\" : " + i + "}", XContentType.JSON).get();
}
ImmutableOpenMap<String, DiscoveryNode> dataNodes = client().admin().cluster().prepareState().get().getState().nodes()
Expand Down Expand Up @@ -275,10 +275,10 @@ public void testCreateShrinkWithIndexSort() throws Exception {
.put("number_of_shards", 8)
.put("number_of_replicas", 0)
)
.addMapping("t1", "id", "type=keyword,doc_values=true")
.addMapping("type", "id", "type=keyword,doc_values=true")
.get();
for (int i = 0; i < 20; i++) {
client().prepareIndex("source", "t1", Integer.toString(i))
client().prepareIndex("source", "type", Integer.toString(i))
.setSource("{\"foo\" : \"bar\", \"id\" : " + i + "}", XContentType.JSON).get();
}
ImmutableOpenMap<String, DiscoveryNode> dataNodes = client().admin().cluster().prepareState().get().getState().nodes()
Expand Down Expand Up @@ -326,7 +326,7 @@ public void testCreateShrinkWithIndexSort() throws Exception {

// ... and that the index sort is also applied to updates
for (int i = 20; i < 40; i++) {
client().prepareIndex("target", randomFrom("t1", "t2", "t3"))
client().prepareIndex("target", "type")
.setSource("{\"foo\" : \"bar\", \"i\" : " + i + "}", XContentType.JSON).get();
}
flushAndRefresh();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ public void testBulkIndexingWhileInitializing() throws Exception {
*/
public void testBulkUpdateDocAsUpsertWithParent() throws Exception {
client().admin().indices().prepareCreate("test")
.setSettings("index.mapping.single_type", false)
.addMapping("parent", "{\"parent\":{}}", XContentType.JSON)
.addMapping("child", "{\"child\": {\"_parent\": {\"type\": \"parent\"}}}", XContentType.JSON)
.execute().actionGet();
Expand Down Expand Up @@ -519,6 +520,7 @@ public void testBulkUpdateDocAsUpsertWithParent() throws Exception {
*/
public void testBulkUpdateUpsertWithParent() throws Exception {
assertAcked(prepareCreate("test")
.setSettings("index.mapping.single_type", false)
.addMapping("parent", "{\"parent\":{}}", XContentType.JSON)
.addMapping("child", "{\"child\": {\"_parent\": {\"type\": \"parent\"}}}", XContentType.JSON));
ensureGreen();
Expand Down Expand Up @@ -603,8 +605,10 @@ public void testBulkUpdateUpsertWithParent() throws Exception {
* Test for https://github.com/elastic/elasticsearch/issues/8365
*/
public void testBulkUpdateChildMissingParentRouting() throws Exception {
assertAcked(prepareCreate("test").addMapping("parent", "{\"parent\":{}}", XContentType.JSON)
.addMapping("child", "{\"child\": {\"_parent\": {\"type\": \"parent\"}}}", XContentType.JSON));
assertAcked(prepareCreate("test")
.setSettings("index.mapping.single_type", false)
.addMapping("parent", "{\"parent\":{}}", XContentType.JSON)
.addMapping("child", "{\"child\": {\"_parent\": {\"type\": \"parent\"}}}", XContentType.JSON));
ensureGreen();

BulkRequestBuilder builder = client().prepareBulk();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,7 @@ public void testAliasFilterWithNowInRangeFilterAndQuery() throws Exception {

public void testAliasesFilterWithHasChildQuery() throws Exception {
assertAcked(prepareCreate("my-index")
.setSettings("index.mapping.single_type", false)
.addMapping("parent")
.addMapping("child", "_parent", "type=parent")
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public void testAddChildTypePointingToAlreadyExistingType() throws Exception {
// Tests _parent meta field logic, because part of the validation is in MetaDataMappingService
public void testAddExtraChildTypePointingToAlreadyParentExistingType() throws Exception {
IndexService indexService = createIndex("test", client().admin().indices().prepareCreate("test")
.setSettings("index.mapping.single_type", false)
.addMapping("parent")
.addMapping("child1", "_parent", "type=parent")
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,9 +319,9 @@ public void testMerge_notAvailable() {

public void testNumberFiltering() {
createIndex("test1", Settings.EMPTY, "type", "value", "type=long");
client().prepareIndex("test1", "test").setSource("value", 1L).get();
client().prepareIndex("test1", "type").setSource("value", 1L).get();
createIndex("test2", Settings.EMPTY, "type", "value", "type=long");
client().prepareIndex("test2", "test").setSource("value", 3L).get();
client().prepareIndex("test2", "type").setSource("value", 3L).get();
client().admin().indices().prepareRefresh().get();

FieldStatsResponse response = client().prepareFieldStats()
Expand Down Expand Up @@ -426,10 +426,10 @@ public void testDateFiltering() {
String dateTime2Str = DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER.parser().print(dateTime2);

createIndex("test1", Settings.EMPTY, "type", "value", "type=date", "value2", "type=date,index=false");
client().prepareIndex("test1", "test")
client().prepareIndex("test1", "type")
.setSource("value", dateTime1Str, "value2", dateTime1Str).get();
createIndex("test2", Settings.EMPTY, "type", "value", "type=date");
client().prepareIndex("test2", "test").setSource("value", dateTime2Str).get();
client().prepareIndex("test2", "type").setSource("value", dateTime2Str).get();
client().admin().indices().prepareRefresh().get();

FieldStatsResponse response = client().prepareFieldStats()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ public void testLatestVersionLoaded() throws Exception {
// clean two nodes
internalCluster().startNodes(2, Settings.builder().put("gateway.recover_after_nodes", 2).build());

assertAcked(client().admin().indices().prepareCreate("test").setSettings("index.mapping.single_type", false));
client().prepareIndex("test", "type1", "1").setSource(jsonBuilder().startObject().field("field", "value1").endObject()).execute().actionGet();
client().admin().indices().prepareFlush().execute().actionGet();
client().prepareIndex("test", "type1", "2").setSource(jsonBuilder().startObject().field("field", "value2").endObject()).execute().actionGet();
Expand Down
11 changes: 6 additions & 5 deletions core/src/test/java/org/elasticsearch/get/GetActionIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.lucene.uid.Versions;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.engine.VersionConflictEngineException;
Expand Down Expand Up @@ -255,7 +254,7 @@ public void testGetDocWithMultivaluedFields() throws Exception {
assertAcked(prepareCreate("test")
.addMapping("type1", mapping1, XContentType.JSON)
.addMapping("type2", mapping2, XContentType.JSON)
.setSettings(Settings.builder().put("index.refresh_interval", -1)));
.setSettings("index.refresh_interval", -1, "index.mapping.single_type", false));
ensureGreen();

GetResponse response = client().prepareGet("test", "type1", "1").get();
Expand Down Expand Up @@ -530,7 +529,7 @@ public void testGetFieldsMetaData() throws Exception {
.addMapping("parent")
.addMapping("my-type1", "_parent", "type=parent", "field1", "type=keyword,store=true")
.addAlias(new Alias("alias"))
.setSettings(Settings.builder().put("index.refresh_interval", -1)));
.setSettings("index.refresh_interval", -1, "index.mapping.single_type", false));

client().prepareIndex("test", "my-type1", "1")
.setRouting("1")
Expand Down Expand Up @@ -594,7 +593,7 @@ public void testGetFieldsNonLeafField() throws Exception {

public void testGetFieldsComplexField() throws Exception {
assertAcked(prepareCreate("my-index")
.setSettings(Settings.builder().put("index.refresh_interval", -1))
.setSettings("index.refresh_interval", -1, "index.mapping.single_type", false)
.addMapping("my-type2", jsonBuilder().startObject().startObject("my-type2").startObject("properties")
.startObject("field1").field("type", "object").startObject("properties")
.startObject("field2").field("type", "object").startObject("properties")
Expand Down Expand Up @@ -726,6 +725,7 @@ public void testUngeneratedFieldsThatAreAlwaysStored() throws IOException {
String createIndexSource = "{\n" +
" \"settings\": {\n" +
" \"index.translog.flush_threshold_size\": \"1pb\",\n" +
" \"index.mapping.single_type\": false," +
" \"refresh_interval\": \"-1\"\n" +
" },\n" +
" \"mappings\": {\n" +
Expand All @@ -738,7 +738,8 @@ public void testUngeneratedFieldsThatAreAlwaysStored() throws IOException {
" }\n" +
" }\n" +
"}";
assertAcked(prepareCreate("test").addAlias(new Alias("alias")).setSource(createIndexSource, XContentType.JSON));
assertAcked(prepareCreate("test")
.addAlias(new Alias("alias")).setSource(createIndexSource, XContentType.JSON));
ensureGreen();

client().prepareIndex("test", "doc").setId("1").setSource("{}", XContentType.JSON).setParent("1").get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.store.RAMDirectory;
import org.elasticsearch.common.compress.CompressedXContent;
import org.elasticsearch.common.lucene.index.ElasticsearchDirectoryReader;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.IndexService;
import org.elasticsearch.index.cache.bitset.BitsetFilterCache;
import org.elasticsearch.index.fielddata.IndexFieldData.XFieldComparatorSource.Nested;
Expand All @@ -41,6 +43,7 @@
import org.elasticsearch.index.mapper.KeywordFieldMapper;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.Mapper.BuilderContext;
import org.elasticsearch.index.mapper.MapperService.MergeReason;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.mapper.NumberFieldMapper;
import org.elasticsearch.index.mapper.ParentFieldMapper;
Expand Down Expand Up @@ -131,7 +134,7 @@ public <IFD extends IndexFieldData<?>> IFD getForField(String type, String field

@Before
public void setup() throws Exception {
indexService = createIndex("test");
indexService = createIndex("test", Settings.builder().put("mapping.single_type", false).build());
mapperService = indexService.mapperService();
indicesFieldDataCache = getInstanceFromNode(IndicesService.class).getIndicesFieldDataCache();
ifdService = indexService.fieldData();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ private static void assertMappingsHaveField(GetMappingsResponse mappings, String
}

public void testMappingsPropagatedToMasterNodeImmediately() throws IOException {
createIndex("index");
assertAcked(prepareCreate("index").setSettings("index.mapping.single_type", false));

// works when the type has been dynamically created
client().prepareIndex("index", "type", "1").setSource("foo", 3).get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.elasticsearch.index.IndexService;
import org.elasticsearch.index.mapper.BooleanFieldMapper.BooleanFieldType;
import org.elasticsearch.index.mapper.DateFieldMapper.DateFieldType;
import org.elasticsearch.index.mapper.MapperService.MergeReason;
import org.elasticsearch.index.mapper.NumberFieldMapper.NumberFieldType;
import org.elasticsearch.test.ESSingleNodeTestCase;

Expand Down Expand Up @@ -524,7 +525,7 @@ public void testReuseExistingMappings() throws IOException, Exception {
}

public void testMixTemplateMultiFieldAndMappingReuse() throws Exception {
IndexService indexService = createIndex("test");
IndexService indexService = createIndex("test", Settings.builder().put("mapping.single_type", false).build());
XContentBuilder mappings1 = jsonBuilder().startObject()
.startObject("type1")
.startArray("dynamic_templates")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
package org.elasticsearch.index.mapper;

import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.compress.CompressedXContent;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentFactory;
Expand All @@ -30,6 +29,7 @@
import org.elasticsearch.index.mapper.MapperService.MergeReason;
import org.elasticsearch.index.mapper.NumberFieldMapper.NumberFieldType;
import org.elasticsearch.test.ESSingleNodeTestCase;
import org.hamcrest.Matchers;

import java.io.IOException;
import java.io.UncheckedIOException;
Expand Down Expand Up @@ -74,7 +74,7 @@ public void testTypeNameTooLong() {
}

public void testTypes() throws Exception {
IndexService indexService1 = createIndex("index1");
IndexService indexService1 = createIndex("index1", Settings.builder().put("index.mapping.single_type", false).build());
MapperService mapperService = indexService1.mapperService();
assertEquals(Collections.emptySet(), mapperService.types());

Expand Down Expand Up @@ -207,7 +207,7 @@ public void testMergeParentTypesSame() {
}

public void testOtherDocumentMappersOnlyUpdatedWhenChangingFieldType() throws IOException {
IndexService indexService = createIndex("test");
IndexService indexService = createIndex("test", Settings.builder().put("index.mapping.single_type", false).build());

CompressedXContent simpleMapping = new CompressedXContent(XContentFactory.jsonBuilder().startObject()
.startObject("properties")
Expand Down Expand Up @@ -309,4 +309,16 @@ public void testIndexSortWithNestedFields() throws IOException {
assertThat(invalidNestedException.getMessage(),
containsString("cannot have nested fields when index sort is activated"));
}

@AwaitsFix(bugUrl="https://github.com/elastic/elasticsearch/pull/24317#issuecomment-297624290")
public void testForbidMultipleTypes() throws IOException {
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type").endObject().endObject().string();
MapperService mapperService = createIndex("test").mapperService();
mapperService.merge("type", new CompressedXContent(mapping), MergeReason.MAPPING_UPDATE, randomBoolean());

String mapping2 = XContentFactory.jsonBuilder().startObject().startObject("type2").endObject().endObject().string();
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
() -> mapperService.merge("type2", new CompressedXContent(mapping2), MergeReason.MAPPING_UPDATE, randomBoolean()));
assertThat(e.getMessage(), Matchers.startsWith("Rejecting mapping update to [test] as the final mapping would have more than 1 type: "));
}
}
Loading

0 comments on commit 1be2800

Please sign in to comment.